1 คะแนน โดย GN⁺ 2025-04-01 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • หากอาศัยช่วงเวลาสั้น ๆ ที่ GITHUB_TOKEN ที่ถูกเปิดเผยยังใช้งานได้ อาจเปิดเส้นทางการโจมตีซัพพลายเชนที่นำไปสู่การรันโค้ดในเวิร์กโฟลว์ GitHub Actions ของหลาย repository ที่ใช้ CodeQL ได้
  • token อยู่ใน workflow artifact ที่ถูกอัปโหลดโดยเวิร์กโฟลว์ debug ที่ล้มเหลวของ repository github/codeql-action และเป็น token สำหรับการติดตั้ง GitHub App ที่มีสิทธิ์ contents: write, actions: write, packages: write
  • ความสำเร็จของการโจมตีขึ้นอยู่กับ race condition ที่ต้องดาวน์โหลดและใช้ token หลังจาก artifact ถูกอัปโหลดแต่ก่อนที่ job จะจบ โดยอ้างอิงจาก log จริงมีเวลาประมาณ 1.022 วินาที และจากการสังเกตสามารถสร้าง branch, push poc.txt และสร้าง tag ได้สำเร็จภายในประมาณ 2 วินาที
  • เนื่องจากการตั้งค่า CodeQL แบบพื้นฐานภายในจะรัน v3 tag ของ github/codeql-action และ tag ดังกล่าวไม่ immutable หากผู้โจมตีย้าย tag ไปยัง commit ที่เป็นอันตราย ก็อาจกระทบถึง repository ที่ใช้เวิร์กโฟลว์ CodeQL แบบพื้นฐานได้
  • GitHub ออก PR เพื่อปิดการอัปโหลด debug artifact หลังจากได้รับรายงานประมาณ 3 ชั่วโมง และกำหนด CVE-2025-24362 โดยระบุใน advisory ว่าไม่พบหลักฐานว่ามีการบุกรุกแพลตฟอร์มหรือระบบ

เส้นทางโจมตีซัพพลายเชนของ CodeQL ที่เริ่มจาก artifact สาธารณะ

  • secret สาธารณะเพียงรายการเดียวอาจนำไปสู่ การโจมตีซัพพลายเชน ต่อ GitHub CodeQL ได้
  • secret ดังกล่าวใช้งานได้ครั้งละเพียงประมาณ 1.022 วินาที แต่ภายในช่วงนั้น ผู้โจมตีสามารถดำเนินขั้นตอนต่าง ๆ เพื่อรันโค้ดภายในเวิร์กโฟลว์ GitHub Actions ได้
  • ขอบเขตผลกระทบอาจครอบคลุมทั้ง GitHub Cloud และ GitHub Enterprise
  • ความเสียหายที่เป็นไปได้มีดังนี้
    • ซอร์สโค้ดรั่วไหล จาก private repository ที่ใช้ CodeQL
    • ขโมย GitHub Actions secrets ภายใน job ของเวิร์กโฟลว์ CodeQL
    • รันโค้ดบนโครงสร้างพื้นฐานภายในที่เวิร์กโฟลว์ CodeQL ทำงานอยู่
    • ขโมย secrets จากเวิร์กโฟลว์ที่ใช้ GitHub Actions Cache ใน repository ที่ใช้ CodeQL
  • อ้างอิงจาก GitHub advisory ทาง GitHub ไม่พบหลักฐานว่ามีการบุกรุกแพลตฟอร์มหรือระบบ

วิธีค้นพบ secret

  • มีการใช้ Actions Artifacts Secret Scanner ซึ่งดาวน์โหลด GitHub Actions workflow artifact แตกไฟล์แบบ recursive แล้วสแกนหา secret ด้วย Nosey Parker
  • ฟังก์ชันนี้ถูกรวมเข้าใน Chariot และเปิดซอร์สเป็นฟีเจอร์ของ Gato
  • หลังสแกนเป็นเวลาหนึ่งวัน พบ token ใน artifact ของ repository github/codeql-action
    • artifact เป้าหมายคือไฟล์ zip my-debug-artifacts ที่ถูกอัปโหลดโดยเวิร์กโฟลว์ “PR Check – Debug artifacts after failure”
    • เมื่อแตกไฟล์ my-db-java-partial.zip ด้านใน ตรวจพบ GitHub Token ที่ขึ้นต้นด้วย ghs_ ใน crash report
    • จากการตรวจสอบด้วยตนเอง พบว่าเป็น token สำหรับการติดตั้ง GitHub App ที่ถูกเก็บไว้ในไฟล์ซึ่งมี environment variables ของ GitHub Runner

โครงสร้างความเชื่อมโยงระหว่าง GitHub Actions, artifact และ CodeQL

  • GitHub Actions เป็น แพลตฟอร์ม CI/CD ที่รันงานซึ่งนิยามไว้ใน YAML workflow บน runner
  • ทุก workflow run จะสร้าง GITHUB_TOKEN โดยอัตโนมัติ ซึ่งเป็น token สำหรับการติดตั้ง GitHub App
    • runner ใช้ token นี้ยืนยันตัวตนกับ GitHub เพื่อทำงานที่เวิร์กโฟลว์ต้องการ
    • สามารถตั้งค่าสิทธิ์ได้ในระดับไฟล์ workflow, repository และ organization
  • GitHub Actions workflow artifact คือไฟล์ที่เวิร์กโฟลว์เก็บไว้ใน GitHub Actions เพื่อใช้ภายหลัง
    • โดยค่าเริ่มต้น ผู้ที่มีสิทธิ์อ่าน repository จะเข้าถึงได้
    • ถูกเก็บไว้ได้นานสูงสุด 90 วัน
  • CodeQL เป็นเอนจินวิเคราะห์โค้ดของ GitHub ใช้สำหรับทำ static analysis ใน GitHub repository เพื่อค้นหาช่องโหว่
  • เครื่องมือความปลอดภัยอย่าง CodeQL บางครั้งต้องเข้าถึงระบบและข้อมูลที่อ่อนไหว หากถูกเจาะอาจกลายเป็น backdoor ได้

สิทธิ์ของ token และ race condition

  • ในขั้นตอน “Setup Job” ของ log เวิร์กโฟลว์ ยืนยันได้ว่า GITHUB_TOKEN ดังกล่าวมีสิทธิ์เขียนทั้งหมด
  • สิทธิ์ที่สำคัญเป็นพิเศษมีดังนี้
    • contents: write: สามารถสร้าง branch, สร้าง tag และอัปโหลด release artifact ได้
    • actions: write: สามารถทริกเกอร์งาน Actions และ event workflow_dispatch ได้
    • packages: write: สามารถอัปโหลด package ได้
  • เนื่องจาก token ใช้งานได้เฉพาะระหว่างที่ workflow job นั้นกำลังทำงาน การโจมตีจึงต้องมีเงื่อนไขสามข้อ
    • token ต้องมีสิทธิ์ write
    • ต้องใช้ upload artifact API v4 เพื่อดึง artifact ได้ก่อน job จบ
    • เวลาระหว่างการอัปโหลด artifact กับการจบ job ต้องเพียงพอสำหรับการดาวน์โหลด token, แตกไฟล์ และใช้งาน
  • CodeQL ไม่ได้ใช้ action actions/upload-artifact โดยตรง แต่ใช้ upload artifact client ด้วยตนเองในซอร์สโค้ด และตาม comment ในโค้ดระบุว่าใช้ version 4
  • ช่วงเวลาตาม raw GitHub log สั้นมาก
    • “Finalizing artifact upload”: 17:22:09.888
    • “Cleaning up orphan processes”: 17:22:10.911
    • คำนวณได้ประมาณ 1.022 วินาที และจากการสังเกต token ยังใช้งานได้หลัง cleanup อีกประมาณ 1 วินาที จึงถือว่ามีเวลาประมาณ 2 วินาที

PoC: การรัน artifact_racer.py

  • มีการเขียนสคริปต์ Python สำหรับตรวจสอบชื่อ artifact_racer.py
  • ลำดับการทำงานมีดังนี้
    • คอย query ว่าเวิร์กโฟลว์ “PR Check – Debug artifacts after failure” เริ่มทำงานใน repository github/codeql-action หรือไม่
    • monitor artifact ของ workflow ที่กำลังรัน
    • เมื่อเห็น artifact ให้ดาวน์โหลดและดึง GITHUB_TOKEN ออกมา
    • ใช้ token ที่ดึงมาเพื่อสร้าง branch ใหม่
    • push ไฟล์ว่างชื่อ poc.txt ไปยัง branch นั้น
    • สร้าง tag ใหม่ให้ commit ดังกล่าว
  • เนื่องจาก artifact มีขนาดประมาณ 21MB จึงประเมินว่ามีโอกาสสำเร็จภายในเวลาที่กำหนด
  • หลังสำเร็จใน repository ทดสอบ จึงนำไปรันกับ repository github/codeql-action
  • ประมาณ 2 ชั่วโมงต่อมา เวิร์กโฟลว์เริ่มทำงาน และสคริปต์สามารถนำ token มาใช้สร้าง branch, push file และสร้าง tag ได้สำเร็จ

การปรับแก้ tag ทำให้ขอบเขตผลกระทบใหญ่ขึ้น

  • ผู้โจมตีสามารถใช้ token ดังกล่าวเพิ่มโค้ดอันตรายลงใน branch ที่ไม่ได้ถูกป้องกันได้
  • วิธีที่แนบเนียนกว่านั้นคือใส่การเปลี่ยนแปลงอันตรายเล็ก ๆ ลงใน feature branch ก่อน merge แล้วรอให้ถูก merge
  • สิทธิ์สร้าง tag ก็สำคัญเช่นกัน
    • หากผูก tag v3 เข้ากับ commit ที่มีโค้ดอันตราย ผู้ใช้ที่เรียกใช้ codeql-action@v3 ด้วยตนเองอาจรันโค้ดอันตรายได้
  • ผลกระทบที่ใหญ่กว่านั้นมาจากการตั้งค่า CodeQL แบบพื้นฐาน
    • เมื่อเปิดใช้ CodeQL ด้วยการตั้งค่าเริ่มต้นใน repository settings จะมีเวิร์กโฟลว์ GitHub Actions พิเศษที่ไม่แสดงในรายการเวิร์กโฟลว์ของ repository ทำงานขึ้นมา
    • เวิร์กโฟลว์นี้ทำการ checkout repository, initialize CodeQL, scan ด้วย CodeQL และอัปโหลดผลลัพธ์
    • ภายในจะรัน action จาก repository github/codeql-action ที่ commit ซึ่ง v3 tag ชี้ไป
  • ในตอนนั้น tag v3 ไม่ได้ immutable และไม่ได้ใช้ workflow pinning ที่ GitHub แนะนำ
  • หากผู้โจมตีย้าย tag v3 ไปยัง commit อันตรายด้วย GITHUB_TOKEN ที่ถูก compromise repository ที่ใช้เวิร์กโฟลว์ CodeQL แบบพื้นฐานอาจรัน CodeQL action ที่เป็นอันตรายได้
  • CodeQL action จะ checkout ซอร์สโค้ดของ repository เป้าหมาย ดังนั้น action อันตรายจึงสามารถทำให้ซอร์สโค้ดของ repository ที่ใช้การตั้งค่า CodeQL พื้นฐานรั่วไหลได้

เส้นทาง GitHub Actions Cache poisoning

  • GITHUB_TOKEN ของ CodeQL action แบบพื้นฐานมีสิทธิ์ read เท่านั้น ดังนั้นหากมีเพียงการตั้งค่าเริ่มต้น จะยังไม่สามารถทำงานที่ต้องเขียน repository, วาง backdoor ใน release หรือขโมย secrets ผ่าน workflow_dispatch ได้ทันที
  • อย่างไรก็ตาม CodeQL action แบบพื้นฐานรันบน main branch ของ repository
  • main branch ของ GitHub repository สามารถเขียน cache entry ที่ใช้ได้ทั่วทั้ง repository จึงทำให้เกิดโอกาส GitHub Actions cache poisoning
  • GitHub Actions Cache Poisoning เป็นเทคนิคที่กล่าวถึงในบทความของ Adnan Khan และ Cacheract คือ malware ที่ทิ้ง persistence ไว้ใน build pipeline ผ่าน cache poisoning
  • หากผู้โจมตี deploy Cacheract ผ่านเวิร์กโฟลว์ CodeQL อาจเกิดลำดับการทำงานดังนี้
    • คาดเดา cache entry
    • เขียนทับ entry นั้นด้วย action อันตราย
    • ได้สิทธิ์รันโค้ดในเวิร์กโฟลว์ที่ใช้ actions/cache
    • ขโมย GitHub Actions secrets และ GITHUB_TOKEN ที่มีสิทธิ์สูงกว่าของเวิร์กโฟลว์นั้น
  • แม้จะพบ CodeQL action อันตรายและแก้ช่องโหว่แล้ว Cacheract ก็ยังสามารถทำ cache poisoning ต่อไปได้
  • พบตัวอย่าง repo สำคัญที่ใช้ทั้ง CodeQL และ actions/cache เช่น Homebrew, Angular, Grafana

CVE-2025-24362 และการแก้ไข

  • ผลจากการเปิดเผยนี้ทำให้มีการกำหนด CVE-2025-24362
  • GITHUB_TOKEN ที่ถูกเปิดเผยอยู่ใน debug artifact ที่ CodeQL Action อัปโหลดหลังจาก code scanning workflow ล้มเหลว
  • repository CodeQL Actions ตั้งใจทำให้เกิดความล้มเหลว แต่ผู้ใช้ CodeQL Actions รายอื่นก็อาจเปิดเผย secrets ของตนเองผ่าน environment variables ของ workflow หากเกิดความล้มเหลวคล้ายกัน
  • ปัญหานี้ได้รับการแก้ไขใน CodeQL Action 3.28.3
  • ผลกระทบที่เป็นไปได้มากที่สุดไม่ได้อยู่ที่ตัว CVE เอง แต่อยู่ที่เส้นทางการใช้ประโยชน์จากช่องโหว่กับ repository CodeQL Actions แล้วทำการโจมตีซัพพลายเชนต่อผู้ใช้ CodeQL

การตอบสนองของ GitHub และมาตรการบรรเทาที่แนะนำ

  • ไทม์ไลน์การตอบสนองของ GitHub มีดังนี้
    • 22 มกราคม 2025 15:13 UTC: ส่งรายงานไปยัง GitHub
    • 22 มกราคม 2025 17:48 UTC: GitHub ยืนยันการรับเรื่อง
    • 22 มกราคม 2025 18:28 UTC: GitHub ยืนยันช่องโหว่และปิดใช้งานเวิร์กโฟลว์ “PR Check – Debug artifacts after failure” ชั่วคราว พร้อมส่ง PR เพื่อปิดการอัปโหลด debug artifact
    • 24 มกราคม 2025: GitHub กำหนด CVE-2025-24362 และเผยแพร่ security advisory
  • มาตรการลดความเสี่ยงจากการเปิดเผย secret ใน GitHub Actions workflow artifact มีดังนี้
    • อัปโหลดเฉพาะไฟล์หรือ directory ที่ระบุเป็น workflow artifact
    • หลีกเลี่ยงการอัปโหลด artifact ที่รวม environment variables, .git/config และไฟล์ใน directory <path_to_runner_dir>/_work/_temp/ ของ runner
    • จำกัด สิทธิ์ GITHUB_TOKEN ให้เป็น read-only
    • ทำ secret scan ก่อนอัปโหลด artifact

1 ความคิดเห็น

 
GN⁺ 2025-04-01
ความคิดเห็นใน Hacker News
  • ถ้า GitHub ผลักดัน immutable actions ให้เร็วขึ้น ผลกระทบแบบนี้ก็คงลดลงไปได้มาก
    ผมพูดซ้ำ ๆ มาตลอดว่าฟีเจอร์นี้น่าจะช่วยปิดพื้นที่โจมตีของ GitHub Actions ได้มากกว่า 70% ในตอนนี้ เห็นมีเหตุเกิดขึ้นทุกสัปดาห์แบบนี้ ก็น่าจะถึงเวลาเปิดตัวแล้ว
    [1] https://github.com/features/preview/immutable-actions

    • ถ้ายังอยู่ในสถานะพรีวิว ก็น่าจะมีเหตุผลของมัน อาจมีบั๊กร้ายแรง ช่องโหว่ด้านความปลอดภัย หรือ compatibility breaking ที่ถ้ารีบปล่อยอาจสร้างความเสียหายมากกว่าเดิม
  • ไม่มีคำอธิบายว่าทำไม โทเค็นชั่วคราว นี้ถึงมีสิทธิ์สร้าง deployment ใหม่และสร้าง artifact attestation ได้ด้วย
    บอกว่าแก้โดยปิด debug log แล้ว แต่ไม่ได้ตอบว่าได้ปรับสิทธิ์ของโทเค็นชั่วคราวให้เหมาะสมกับเอนจินวิเคราะห์โค้ดมากขึ้นหรือไม่

    • เหมือนเรื่องเก่าที่ทุกคนรู้กันดี วิศวกรที่กำลังสร้างมันเจอ permission denied เลยให้สิทธิ์ทุกอย่าง แล้วก็ไม่เคยกลับมาลดให้เป็น least privilege ทีหลัง
    • ถ้าโทเค็นชั่วคราวของ GitHub Actions ไม่มีขอบเขตสิทธิ์ที่กำหนดไว้ใน workflow ก็จะใช้ขอบเขตเริ่มต้นแบบ permissive หรือ restrictive ตามการตั้งค่าของ repository การตั้งค่านี้ยังสามารถกำหนดในระดับองค์กรให้จำกัดทุก repository ได้ด้วย
      ในอดีต ค่าเริ่มต้นมีแต่แบบ permissive เท่านั้น ดังนั้นองค์กรและ repository เก่า ๆ จึงมักใช้การตั้งค่านี้อย่างน่าเสียดาย
      เมื่อสร้าง repository ใหม่ มันจะสืบทอดค่าเริ่มต้นจากองค์กรแม่ ดังนั้นถ้าไม่มีใครเปลี่ยน ค่าเริ่มต้นที่ไม่ปลอดภัยนี้ก็จะยังคงอยู่ต่อไป ไม่มีการตั้งค่ารวมระดับผู้ใช้ ดังนั้น repository ใหม่ที่เจ้าของเป็นบุคคลจะใช้ค่าเริ่มต้นแบบ restrictive ส่วนองค์กรที่สร้างใหม่ก็เข้าใจว่าใช้ค่าเริ่มต้นที่ดีกว่าเช่นกัน
      [0]: https://docs.github.com/en/actions/security-for-github-actio...
    • โทเค็นชั่วคราวของ Actions โดยค่าเริ่มต้นมี สิทธิ์เขียนทั้งหมด หากต้องการใช้เวอร์ชันอ่านอย่างเดียว ต้องเลือกอย่างชัดเจน

      Read and write permissions
      Workflows have read and write permissions in the repository for all scopes.
      ถ้าอ่านแค่บรรทัดนี้ในเอกสาร(https://docs.github.com/en/actions/security-for-github-actio...) อาจคิดต่างออกไปได้
      If the default permissions for the GITHUB_TOKEN are restrictive, you may have to elevate the permissions to allow some actions and commands to run successfully.
      แต่ในองค์กร GitHub ของเรา "Read and write permissions" เป็นค่าเริ่มต้น จึงยืนยันได้ว่าประโยคในเอกสารนั้นไม่สมเหตุสมผล

    • การ แฮ็ก Microsoft ในปี 2023 ก็คล้ายกัน เหตุการณ์นั้น CISA ออกมาตำหนิต่อสาธารณะว่าความปลอดภัยแย่มาก และแม้แต่บล็อกโพสต์ที่ Microsoft เขียนเพื่ออธิบายเหตุการณ์ ก็ยังมีคำถามมากมายที่ไม่ได้ตอบ
    • หวังว่าการปิด debug log จะเป็นแค่มาตรการชั่วคราวแบบเร่งด่วน ไม่ควรเป็นการแก้ไขสุดท้าย
  • ยิ่งมั่นใจมากขึ้นว่า CI และ CD ควรเป็นสภาพแวดล้อมที่แยกจากกันโดยสิ้นเชิง ต่อให้ CI ถูกเจาะ ก็ไม่ควรทำให้โทเค็นที่เกี่ยวกับ CD รั่วไหล

    • เรื่องนี้เป็นด้านที่ผมสนใจมากเป็นการส่วนตัว และผมมองว่าทางแก้ไม่ใช่การแยก CD ออกไปเป็นระบบเฉพาะขนาดเล็กอีกระบบหนึ่ง แต่เป็นการทำให้ CD เหมือน multi-factor มากกว่า
      เช่น บังคับให้ต้องตรงตามเงื่อนไขอย่าง "sub":"repo:octo-org/octo-repo:environment:prod"[1] และถ้าอยากทำให้ระบบแข็งแรงขึ้น ก็เพิ่ม [fun claims][] อื่น ๆ ได้
      1: https://docs.github.com/en/actions/security-for-github-actio...
      fun claims: https://github.com/github/actions-oidc-debugger#readme
    • โดยแก่นแล้ว นี่คือภาพของ การแยกหน้าที่และความรับผิดชอบ โปรเจกต์ดี ๆ บางตัวก็ทำงานในลักษณะนี้ เทคนิค เครื่องมือ และเส้นแบ่งระหว่าง CI กับ CD อาจแตกต่างกันไปตามลักษณะของผลลัพธ์สุดท้าย แต่ในเชิงแนวคิดถือว่าถูกต้องอย่างยิ่ง
  • เวลาในการตอบสนองไม่ใช่เรื่องเล่น ๆ เลย การตอบสนองของ GitHub ค่อนข้างน่าประทับใจ

    • การที่โทเค็นสาธารณะที่มี สิทธิ์เขียนทั้งหมด ถูกเปิดเผยออกมา ไม่น่าประทับใจเท่าไร
  • ในฐานะคนที่มีนามสกุล Prater เพราะชื่อนี้มีที่มาจาก Praetorian เลยคิดว่าถ้าได้ครอบครอง praetorian.com คงดีมาก

    • ถ้าจะทำแบบนั้น ต้องคิดให้ได้ก่อนที่ภาพยนตร์ปี 1995 เรื่อง “The Net” จะออกฉาย
    • โปรเจกต์ gokart ของพวกเขายอดเยี่ยมมาก
  • การใช้ GitHub Actions สาธารณะคือการหาเรื่องใส่ตัว ยิ่งถ้าไม่วิเคราะห์ขั้นตอนใน workflow ก็ยิ่งใช่
    โฮสต์ woodpecker เอง หรือ CI builder ดี ๆ ตัวอื่น ๆ (circle, travis, gitlab ฯลฯ) เองยังดีกว่า

  • เราใช้ CodeQL กับ PR ของ OpenZFS ไว้แล้ว เรื่องนี้ไม่เป็นปัญหาสำหรับ OpenZFS เพราะโค้ดของเราไม่ใช่ความลับ :)

    • ต่อให้โค้ดไม่ใช่ความลับ ผมก็ไม่คิดว่าเป็นการตัดสินใจที่ดี เพราะผู้โจมตีสามารถเพิ่มอะไรก็ได้ลงในโค้ดหรือ release artifact
      แต่อย่างน้อยก็ดีที่แก้ได้รวดเร็ว
  • อันนี้แก้แล้วหรือยัง?

    • ตามที่ระบุไว้ในบทความและคอมเมนต์ที่นี่ ใช่ GitHub บรรเทาปัญหาภายใน 3 ชั่วโมง หลังถูกรายงานในเดือนมกราคม
  • เว็บไซต์นี้ประสิทธิภาพแย่มากจนแทบเลื่อนไม่ได้