ภัยคุกคามการโจมตีซัพพลายเชนของ CodeQL – เหตุการณ์ความปลอดภัยที่เริ่มจากคีย์ลับ GitHub ที่ถูกเปิดเผย
(praetorian.com)- หากอาศัยช่วงเวลาสั้น ๆ ที่ 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
- artifact เป้าหมายคือไฟล์ zip
โครงสร้างความเชื่อมโยงระหว่าง 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 และ eventworkflow_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 ดังกล่าว
- คอย query ว่าเวิร์กโฟลว์ “PR Check – Debug artifacts after failure” เริ่มทำงานใน repository
- เนื่องจาก 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ด้วยตนเองอาจรันโค้ดอันตรายได้
- หากผูก tag
- ผลกระทบที่ใหญ่กว่านั้นมาจากการตั้งค่า 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 ความคิดเห็น
ความคิดเห็นใน Hacker News
ถ้า GitHub ผลักดัน immutable actions ให้เร็วขึ้น ผลกระทบแบบนี้ก็คงลดลงไปได้มาก
ผมพูดซ้ำ ๆ มาตลอดว่าฟีเจอร์นี้น่าจะช่วยปิดพื้นที่โจมตีของ GitHub Actions ได้มากกว่า 70% ในตอนนี้ เห็นมีเหตุเกิดขึ้นทุกสัปดาห์แบบนี้ ก็น่าจะถึงเวลาเปิดตัวแล้ว
[1] https://github.com/features/preview/immutable-actions
ไม่มีคำอธิบายว่าทำไม โทเค็นชั่วคราว นี้ถึงมีสิทธิ์สร้าง deployment ใหม่และสร้าง artifact attestation ได้ด้วย
บอกว่าแก้โดยปิด debug log แล้ว แต่ไม่ได้ตอบว่าได้ปรับสิทธิ์ของโทเค็นชั่วคราวให้เหมาะสมกับเอนจินวิเคราะห์โค้ดมากขึ้นหรือไม่
ในอดีต ค่าเริ่มต้นมีแต่แบบ permissive เท่านั้น ดังนั้นองค์กรและ repository เก่า ๆ จึงมักใช้การตั้งค่านี้อย่างน่าเสียดาย
เมื่อสร้าง repository ใหม่ มันจะสืบทอดค่าเริ่มต้นจากองค์กรแม่ ดังนั้นถ้าไม่มีใครเปลี่ยน ค่าเริ่มต้นที่ไม่ปลอดภัยนี้ก็จะยังคงอยู่ต่อไป ไม่มีการตั้งค่ารวมระดับผู้ใช้ ดังนั้น repository ใหม่ที่เจ้าของเป็นบุคคลจะใช้ค่าเริ่มต้นแบบ restrictive ส่วนองค์กรที่สร้างใหม่ก็เข้าใจว่าใช้ค่าเริ่มต้นที่ดีกว่าเช่นกัน
[0]: https://docs.github.com/en/actions/security-for-github-actio...
ยิ่งมั่นใจมากขึ้นว่า CI และ CD ควรเป็นสภาพแวดล้อมที่แยกจากกันโดยสิ้นเชิง ต่อให้ CI ถูกเจาะ ก็ไม่ควรทำให้โทเค็นที่เกี่ยวกับ CD รั่วไหล
เช่น บังคับให้ต้องตรงตามเงื่อนไขอย่าง
"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
เวลาในการตอบสนองไม่ใช่เรื่องเล่น ๆ เลย การตอบสนองของ GitHub ค่อนข้างน่าประทับใจ
ในฐานะคนที่มีนามสกุล Prater เพราะชื่อนี้มีที่มาจาก Praetorian เลยคิดว่าถ้าได้ครอบครอง praetorian.com คงดีมาก
การใช้ GitHub Actions สาธารณะคือการหาเรื่องใส่ตัว ยิ่งถ้าไม่วิเคราะห์ขั้นตอนใน workflow ก็ยิ่งใช่
โฮสต์ woodpecker เอง หรือ CI builder ดี ๆ ตัวอื่น ๆ (circle, travis, gitlab ฯลฯ) เองยังดีกว่า
เราใช้ CodeQL กับ PR ของ OpenZFS ไว้แล้ว เรื่องนี้ไม่เป็นปัญหาสำหรับ OpenZFS เพราะโค้ดของเราไม่ใช่ความลับ :)
แต่อย่างน้อยก็ดีที่แก้ได้รวดเร็ว
อันนี้แก้แล้วหรือยัง?
เว็บไซต์นี้ประสิทธิภาพแย่มากจนแทบเลื่อนไม่ได้