DEV Community

Ice
Ice

Posted on

GhostAction กลับมาอีกรอบ: เมื่อ workflow ชื่อ "security-audit" คือตัวขโมย secret และสิ่งที่ทีม DevSecOps ควรเช็คเดี๋ยวนี้

เช้าวันจันทร์ เปิดข่าวมาเจอเรื่องนี้เลยต้องวางกาแฟแล้วไปไล่เช็ค repo ตัวเองก่อน เรื่องคือแคมเปญ GhostAction ที่เคยเป็นข่าวเมื่อกันยายนปีที่แล้ว กลับมาอีกรอบ รอบนี้ The Hacker News รายงานเมื่อ 9 ตุลาคม 2026 ว่ามี workflow ไฟล์หนึ่งถูกยัดเข้าไปใน repository เป็นหลักหมื่น และเป้าหมายของมันคือสิ่งเดียวที่ทีม CI/CD ทุกทีมมีเหมือนกัน นั่นคือ secret ที่เก็บไว้ใน GitHub Actions

บทความนี้จะแยกเป็นสามส่วน: ข่าวมันเกิดอะไรขึ้นจริง (เท่าที่แหล่งข่าวยืนยัน), ทำไมวิธีนี้ถึงได้ผลกับทีมที่คิดว่าตัวเอง "ก็ตั้งค่าปลอดภัยแล้ว", และถ้าเป็นเราต้องทำอะไรบ้างทั้งวันนี้และระยะยาว ทุกอย่างในนี้เป็นแนวป้องกัน ไม่มีขั้นตอนโจมตี

ข่าวคืออะไร

ข้อมูลหลักมาจากรายงานของ The Hacker News อ้าง StepSecurity, Socket และ GitGuardian สรุปเฉพาะที่ยืนยันได้:

  • Socket นับได้ว่ามีบัญชี GitHub มากกว่า 500 บัญชี commit workflow ตัวนี้เข้าไปใน repository หลักหมื่น นับตั้งแต่ 7 ตุลาคม 2026
  • StepSecurity บันทึกสองเคสของบัญชี maintainer ที่ถูกใช้งาน เคสแรก workflow ถูก push เข้า 27 repository เริ่มประมาณ 13:20 UTC อีกแปดชั่วโมงถัดมาอีกบัญชีหนึ่งถูก push เข้า 318 repository ภายในเวลาแค่ราว 16 นาที
  • GitGuardian รายงานเฟสก่อนหน้า: 772 public repository ของ 373 ผู้ใช้/องค์กร ระหว่าง 31 สิงหาคมถึง 30 กันยายน 2026
  • แคมเปญรอบแรก (2025) เคยเก็บ secret ไปได้ 3,325 รายการจาก 817 repository

วิธีเข้าถึงที่แหล่งข่าวประเมินว่าเป็นไปได้มากที่สุดคือ personal access token ของ maintainer รั่ว อาจมาจาก infostealer log หรือ credential dump ไม่ใช่ช่องโหว่ของ GitHub เอง แปลว่าต่อให้ GitHub ปลอดภัยแค่ไหน token ที่หลุดอยู่นอกแพลตฟอร์มก็ยังเป็นประตูหน้าบ้านอยู่ดี

หน้าตาของมัน และทำไมถึงเนียน

Workflow ที่ถูกยัดมีชื่อไฟล์ว่า security-audit.yml หรือ github_actions_security.yml ชื่อแบบนี้เองคือจุดที่น่าสนใจ มันไม่ได้ตั้งชื่อว่า steal.yml แต่แกล้งเป็นงาน security ที่ใครเห็นในรายการ workflow ก็อาจคิดว่า "อ๋อ ทีมเพิ่มตัวสแกนมั้ง" แล้วปล่อยผ่าน

ลักษณะทางเทคนิคที่แหล่งข่าวบรรยายไว้ ซึ่งเอาไปใช้เป็นตัวตรวจจับได้:

  • trigger เป็น workflow_dispatch และ push ทุก branch ทุก tag โดยไม่กรอง
  • checkout ด้วย fetch-depth: 0 คือดึง git history ทั้งหมดมา ไม่ใช่แค่ commit ล่าสุด
  • มี step เดียวชื่อ "Audit" ที่ส่งข้อมูลออกไปยัง IP ฮาร์ดโค้ดผ่าน HTTP ธรรมดา (IOC ที่รายงานคือ 193.32.204[.]199)
  • เป้าหมายคือ secret ที่ตั้งไว้ใน Actions, credential ที่อยู่ใน working tree และที่ซ่อนอยู่ใน git history รวมถึง AWS key, token ของ GitHub/GitLab และ API key ของฝั่ง AI อย่าง OpenAI, Anthropic, OpenRouter

ประเด็นที่ผมอยากให้สังเกตคือ fetch-depth: 0 ตรงนี้เป็นเรื่องที่คนมักมองข้าม เราชอบคิดว่า "secret ที่เคย commit พลาดแล้วลบไปแล้ว" ปลอดภัย แต่ใน history มันยังอยู่ครบ workflow ที่ดึง history ทั้งก้อนแล้วเอาไป grep คือการเก็บเกี่ยวหนี้เก่าทั้งหมดของ repo ในรอบเดียว

อีกเรื่องที่น่าคิด: GitGuardian บอกว่าแคมเปญเล็งไปถึง 2,577 รูปแบบของ secret ตั้งแต่ SSH key, Azure, Docker Hub/GHCR, database, GCP/Firebase, bot token ของ Slack/Discord/Telegram ไปจนถึง credential ของ npm และ PyPI ตอนนี้แหล่งข่าวบอกว่ายังไม่พบการ publish package ด้วย credential ที่ขโมยไป แต่คนที่ดูแล package ควรถือว่าเป็นความเสี่ยงที่ใกล้ที่สุด

เช็คอะไรวันนี้ (ใช้เวลาไม่เกิน 30 นาที)

ถ้าคุณดูแลหลาย repo ใน organization ให้เริ่มจากชุดคำสั่งง่ายๆ นี้ ใช้ gh CLI ที่ login ด้วยสิทธิ์อ่านอย่างเดียวพอ

# หา workflow ชื่อต้องสงสัยในทุก repo ของ org
ORG=your-org
gh repo list "$ORG" --limit 1000 --json nameWithOwner -q '.[].nameWithOwner' | while read -r repo; do
  for f in security-audit.yml github_actions_security.yml; do
    if gh api "repos/$repo/contents/.github/workflows/$f" >/dev/null 2>&1; then
      echo "FOUND: $repo/$f"
    fi
  done
done
Enter fullscreen mode Exit fullscreen mode

แล้วเช็ค commit ล่าสุดที่แตะโฟลเดอร์ .github/workflows ว่าใครเป็นคน push ตรงเข้า default branch โดยไม่ผ่าน pull request:

gh api "repos/$ORG/REPO/commits?path=.github/workflows&since=2026-08-31T00:00:00Z" \
  -q '.[] | [.sha[0:7], .commit.author.name, .commit.message] | @tsv'
Enter fullscreen mode Exit fullscreen mode

ถ้าเจอไฟล์จริง แหล่งข่าวแนะนำให้ถือว่าถูกบุกรุกแล้ว ลำดับที่ผมจะทำคือ:

  1. revoke token/PAT ของบัญชีที่ push เข้ามา ก่อนอย่างอื่น
  2. ลบ workflow ออกจากทุก branch (ไม่ใช่แค่ default branch เพราะ trigger ของมันดักทุก branch)
  3. หมุน secret ทั้งหมดที่ repo นั้นเข้าถึงได้ ทั้ง Actions secrets, cloud key, API key ที่เคยอยู่ใน history ให้ถือว่าหลุดหมด
  4. ไล่ดู CloudTrail / audit log ของ provider ว่ามี key เหล่านั้นถูกเรียกจากที่แปลกๆ ช่วงที่ผ่านมาไหม
  5. เช็ค fork โดยเฉพาะ private fork และ mirror เพราะ Socket เตือนว่า fork ที่สร้างหรือ sync ตามหลังจะรับ workflow นั้นไปด้วย

ป้องกันระยะยาว: เปลี่ยนจาก "secret ที่เก็บไว้" เป็น "สิทธิ์ที่ออกให้ชั่วคราว"

ถ้าให้เลือกข้อเดียวจากบทความนี้ ผมเลือกข้อนี้: ถ้า secret ไม่ได้มีอยู่ใน repo ตั้งแต่แรก ต่อให้ workflow ตัวร้ายถูกยัดเข้ามา ก็ไม่มีอะไรให้ขโมย

1. ใช้ OIDC แทน static cloud key

สำหรับ AWS ให้ workflow ขอ role แบบชั่วคราวผ่าน OIDC แทนการเก็บ AWS_ACCESS_KEY_ID ไว้ใน secrets

permissions:
  id-token: write
  contents: read

steps:
  - uses: aws-actions/configure-aws-credentials@<pin-to-full-sha>
    with:
      role-to-assume: arn:aws:iam::123456789012:role/ci-deploy
      aws-region: ap-southeast-1
Enter fullscreen mode Exit fullscreen mode

แล้วที่ trust policy ของ role ล็อก sub ให้เจาะจงถึง repo และ branch เช่น repo:your-org/your-repo:ref:refs/heads/main อย่าใช้ wildcard กว้างๆ ไม่งั้น workflow ที่ถูกยัดใน branch อื่นก็ขอ role ได้เหมือนกัน (ตัวเลข account ด้านบนเป็นตัวอย่างสมมติ)

2. ให้ GITHUB_TOKEN มีสิทธิ์น้อยที่สุดเป็นค่าเริ่มต้น

ตั้งที่ระดับ org/repo ให้ default เป็น read-only แล้วเปิดเพิ่มเป็นราย job เท่านั้น ใน workflow เองก็ใส่ permissions: ระบุชัดทุกไฟล์

3. บังคับ review กับโฟลเดอร์ workflow

ใช้ CODEOWNERS ชี้ .github/workflows/ ให้เป็นของทีม platform/security และเปิด branch protection ให้ต้องมี approval รวมถึง "do not allow bypassing" สำหรับ admin ด้วย กรณีนี้ attacker ใช้ identity ของ maintainer จริงๆ push ตรง ถ้า default branch ปิดการ push ตรง workflow ที่ยัดมาจะต้องผ่านคนอื่นก่อน

4. Pin action ด้วย commit SHA และจำกัดว่า action ไหนรันได้

ข้อนี้ไม่ได้กัน GhostAction รอบนี้โดยตรง (เพราะมันเป็น workflow ของตัวเอง ไม่ใช่ action ภายนอก) แต่เป็นเรื่องเดียวกันคือการลดพื้นที่ที่คนนอกแทรกโค้ดเข้า pipeline ได้ ตั้ง policy ระดับ org ให้อนุญาตเฉพาะ action ที่ approve แล้ว

5. ดักที่ปลายทาง: egress และ secret scanning

Self-hosted runner ควรมี egress policy ให้ออกได้เฉพาะปลายทางที่จำเป็น ในข่าวนี้ข้อมูลถูกส่งผ่าน HTTP ไปยัง IP เปล่าๆ ซึ่งกฎ egress ธรรมดาก็จับได้ ส่วน GitHub-hosted runner ให้ดักด้วย audit log stream ไปที่ SIEM แล้วตั้ง alert เมื่อมีไฟล์ใหม่ใน .github/workflows ที่ไม่ได้มาจาก pull request

และเปิด secret scanning กับ push protection ไว้ แต่อย่าหยุดแค่นั้น ให้สแกน history เก่าด้วยเครื่องมืออย่าง Gitleaks เป็นรอบๆ เพราะของที่เคยหลุดแล้วลบ ถ้ายังไม่ได้หมุน ก็ยังเป็นของที่ใช้ได้

ความเห็นส่วนตัวหลังไล่ข่าวนี้

สิ่งที่ผมได้จากข่าวนี้ไม่ใช่ "attacker เก่ง" เพราะเทคนิคมันเรียบมาก ไม่มี zero-day ไม่มีอะไรหรูหรา สิ่งที่ได้ผลคือสามอย่างที่เราเองเป็นคนสร้างไว้: PAT อายุยาวที่ไม่มีใครหมุน, secret ที่อยู่ใน repo ตลอดกาลรวมถึงใน history, และสิทธิ์ push ตรงเข้า default branch ที่ไม่มีใครถาม

ผมเองเคยนั่งไล่ลบ PAT เก่าของทีมแล้วเจออันที่สร้างไว้ตั้งแต่สามปีก่อน สิทธิ์ครบ ไม่มีวันหมดอายุ เจ้าของเปลี่ยนงานไปแล้ว (ขอเล่าแบบไม่ลงรายละเอียดนะ) ความรู้สึกตอนนั้นคือเย็นสันหลังวาบ ถ้าวันนั้นมันหลุดไปอยู่ใน infostealer log ใครสักคน เรื่องที่อ่านวันนี้อาจเป็นเรื่องของเราเอง

เลยอยากฝากเป็นการบ้านสั้นๆ ถ้าอ่านจบแล้วยังไม่รู้จะเริ่มตรงไหน: เปิดหน้า Personal access tokens ของตัวเอง ดูว่ามีอันไหนที่ไม่ได้ใช้แล้วหรือไม่มีวันหมดอายุ ลบเดี๋ยวนี้เลย แล้วค่อยกลับมาคุยเรื่อง OIDC กัน

สรุปเช็คลิสต์

ภาพสรุปด้านล่างเอาไปแปะเตือนทีมได้เลย

เช็คลิสต์รับมือ GhostAction


แหล่งอ้างอิงข่าว: The Hacker News (9 ต.ค. 2026) "Credential-Stealing GitHub Actions Workflows Planted in Tens of Thousands of Repositories", GitGuardian blog "GhostAction Returns: 772 Repos Hit in New GitHub Actions Wave"

Credential-Stealing GitHub Actions Workflows Planted in Tens of Thousands of Repositories

More than 500 GitHub accounts committed credential-stealing workflows to tens of thousands of repositories since October 7, Socket says.

favicon thehackernews.com

Top comments (1)

Collapse
 
yuhehe profile image
Yuhe He •

Good roundup — the detail I would add for readers auditing their own repos: the exfil reads FULL git history, not just current secrets, so the audit question is not "do I have secrets today" but "when did the fake workflow land". git log --diff-filter=A --format=%ci -- .github/workflows/security-audit.yml gives you the injection date in one line; rotate everything that existed on that date, not just what is live now. Rotating current secrets does not revive history-leaked ones — that is the step teams skip.