1 คะแนน โดย GN⁺ 2 시간 전 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • ระหว่าง 2026-05-11 19:20~19:26 UTC ผู้โจมตีได้เผยแพร่เวอร์ชันอันตราย 84 เวอร์ชัน ครอบคลุมแพ็กเกจ npm @tanstack/ จำนวน 42 แพ็กเกจ
  • ห่วงโซ่การโจมตีผสาน pull_request_target แบบ “Pwn Request”, การปนเปื้อนแคชของ GitHub Actions และการดึง OIDC token จากหน่วยความจำของ runner
  • npm token และเวิร์กโฟลว์ publish ไม่ได้ถูกขโมยหรือถูกเจาะ โดยมัลแวร์ส่ง POST ไปยัง registry โดยตรงด้วยสิทธิ์ OIDC trusted publisher
  • หากติดตั้งเวอร์ชันที่ได้รับผลกระทบ อาจทำให้ ข้อมูลรับรอง AWS, GCP, Kubernetes, Vault, GitHub, npm, SSH รั่วไหล และจำเป็นต้องเปลี่ยนใหม่
  • เวอร์ชันที่ได้รับผลกระทบทั้งหมดถูกทำเครื่องหมาย deprecated แล้ว และได้ดำเนินการร่วมกับ npm security เพื่อลบ tarball พร้อมเผยแพร่ issue ติดตามและ GitHub Security Advisory

ภาพรวมของเหตุการณ์

  • ระหว่าง 2026-05-11 19:20~19:26 UTC ผู้โจมตีได้เผยแพร่เวอร์ชันอันตราย 84 เวอร์ชัน ครอบคลุมแพ็กเกจ npm @tanstack/* จำนวน 42 แพ็กเกจ
  • ห่วงโซ่การโจมตีผสาน แพตเทิร์น pull_request_target “Pwn Request”, การปนเปื้อนแคชของ GitHub Actions ที่ข้ามขอบเขตความไว้วางใจระหว่าง fork↔base และการดึง OIDC token จากหน่วยความจำของโปรเซส GitHub Actions runner
  • ยืนยันแล้วว่า npm token ไม่ได้ถูกขโมย และตัวเวิร์กโฟลว์ npm publish เองก็ไม่ได้ถูกเจาะ
  • เวอร์ชันอันตรายถูกตรวจพบแบบสาธารณะภายใน 20 นาทีโดยนักวิจัยภายนอก ashishkurmi จาก stepsecurity
  • เวอร์ชันที่ได้รับผลกระทบทั้งหมดถูกทำเครื่องหมาย deprecated แล้ว และกำลังดำเนินการร่วมกับ npm security เพื่อลบ tarball ออกจาก registry
  • ผู้ใช้ที่ติดตั้งเวอร์ชันที่ได้รับผลกระทบในวันที่ 2026-05-11 ต้องเปลี่ยน ข้อมูลรับรอง AWS, GCP, Kubernetes, Vault, GitHub, npm, SSH ที่โฮสต์เครื่องติดตั้งนั้นเข้าถึงได้
  • issue ติดตามคือ TanStack/router#7383 และ GitHub Security Advisory คือ GHSA-g7cv-rxg3-hmpx

ขอบเขตผลกระทบ

  • แพ็กเกจที่ได้รับผลกระทบ

    • ขอบเขตผลกระทบครอบคลุม 42 แพ็กเกจและ 84 เวอร์ชัน โดยมีการเผยแพร่ 2 เวอร์ชันต่อแพ็กเกจ ห่างกันประมาณ 6 นาที
    • รายการทั้งหมดอยู่ใน issue ติดตาม
    • กลุ่มผลิตภัณฑ์ที่ยืนยันว่าไม่ได้รับผลกระทบคือ @tanstack/query*, @tanstack/table*, @tanstack/form*, @tanstack/virtual*, @tanstack/store, เมตาแพ็กเกจ @tanstack/start
    • @tanstack/start-* ไม่ได้รวมอยู่ในรายการที่ยืนยันว่าไม่ได้รับผลกระทบ
  • การทำงานของมัลแวร์

    • เมื่อสภาพแวดล้อมนักพัฒนาหรือ CI รัน npm install, pnpm install, yarn install กับเวอร์ชันที่ได้รับผลกระทบ npm จะประมวลผลรายการ optionalDependencies ที่เป็นอันตรายและดึง orphan payload commit จาก fork network
    • จากนั้นสคริปต์ lifecycle prepare จะถูกรัน และ router_init.js ที่ถูกทำให้อ่านยากขนาดประมาณ 2.3MB ซึ่งซ่อนอยู่ใน tarball ที่ได้รับผลกระทบจะเริ่มทำงาน
    • สคริปต์อันตรายจะรวบรวมข้อมูลรับรองจากตำแหน่งทั่วไป เช่น AWS IMDS/Secrets Manager, GCP metadata, Kubernetes service-account token, Vault token, ~/.npmrc, GitHub token, gh CLI, .git-credentials, SSH private key
    • ข้อมูลที่ขโมยได้จะถูกส่งออกผ่าน เครือข่ายอัปโหลดไฟล์ของ Session/Oxen messenger โดยปลายทางคือ filev2.getsession.org, seed{1,2,3}.getsession.org
    • เนื่องจากเครือข่ายดังกล่าวเข้ารหัสแบบ end-to-end และไม่มี C2 ที่ผู้โจมตีควบคุม มาตรการบรรเทาทางเครือข่ายจึงมีเพียงการบล็อก IP/โดเมนเท่านั้น
    • ลอจิกการแพร่กระจายตัวเองจะไล่รายการแพ็กเกจอื่นที่เหยื่อเป็นผู้ดูแลผ่าน registry.npmjs.org/-/v1/search?text=maintainer:<user> แล้วเผยแพร่ซ้ำด้วยวิธีการฝังแบบเดียวกัน
    • เนื่องจาก payload ทำงานเป็นส่วนหนึ่งของ lifecycle การติดตั้ง npm โฮสต์ที่ติดตั้งเวอร์ชันที่ได้รับผลกระทบในวันที่ 2026-05-11 ควรถูกปฏิบัติว่าอาจถูกเจาะแล้ว

ไทม์ไลน์

  • ก่อนการโจมตี: ขั้นแคชปนเปื้อน

    • เมื่อ 2026-05-10 17:16 UTC ผู้โจมตีสร้าง github.com/zblgg/configuration ซึ่งเป็น fork ของ TanStack/router และเปลี่ยนชื่อเพื่อหลีกเลี่ยงการค้นหาในรายการ fork
    • เมื่อ 2026-05-10 23:29 UTC มีการเขียนคอมมิตอันตราย 65bf499d16a5e8d25ba95d69ec9790a6dd4a1f14 ลงใน fork โดยใช้อัตลักษณ์ที่ถูกปลอมแปลง claude <claude@users.noreply.github.com>
    • คอมมิตดังกล่าวเพิ่ม packages/history/vite_setup.mjs ซึ่งเป็น bundle JS payload ขนาดราว 30,000 บรรทัด และใส่ [skip ci] ไว้ในข้อความคอมมิตเพื่อยับยั้ง CI ของ push event
    • ราว 2026-05-11 10:49 UTC zblgg เปิด PR #7378 ไปยัง main ของ TanStack/router ในชื่อ “WIP: simplify history build”
    • ทั้ง bundle-size.yml และ labeler.yml ถูกเรียกทำงานอัตโนมัติสำหรับ PR ด้วย pull_request_target และ pull_request_target ข้าม approval gate สำหรับผู้มีส่วนร่วมครั้งแรกได้ จึงไม่ต้องมีการอนุมัติเพิ่มเติม
    • pr.yml ที่ใช้ pull_request ถูกบล็อกไว้ในสถานะรออนุมัติและไม่ได้รัน
    • ระหว่าง 2026-05-11 11:01~11:11 UTC zblgg ทำ force-push ไปยัง PR head หลายครั้งเพื่อกระตุ้นให้เกิดการรัน pull_request_target เพิ่มเติม
    • เมื่อ 2026-05-11 11:11 UTC คอมมิตอันตราย 65bf499d ถูกนำขึ้นไปยัง PR head และ job benchmark-pr ของ bundle-size.yml ได้ checkout refs/pull/7378/merge จากนั้นรัน pnpm install และ pnpm nx run @benchmarks/bundle-size:build ทำให้ vite_setup.mjs ถูกเรียกทำงาน
    • เมื่อ 2026-05-11 11:29 UTC มีการบันทึก GitHub Actions cache ขนาด 1.1GB ชื่อ Linux-pnpm-store-6f9233a50def742c09fde54f56553d6b449a535adf87d4083690539f49ae4da11 ไว้ใน TanStack/router
    • แคชนี้ถูกเก็บไว้ใน scope refs/heads/main และถูกจัดให้ตรงกับคีย์ที่ release.yml จะดึงมาใช้ใน push ของ main ครั้งถัดไป
    • เมื่อ 2026-05-11 11:31 UTC ผู้โจมตีรีเซ็ต PR กลับไปที่ b1c061af ซึ่งเป็น main HEAD ณ ตอนนั้น เพื่อให้ PR ที่มองเห็นดูเหมือน no-op 0 ไฟล์ และในนาทีเดียวกันก็ปิด PR และลบ branch แต่แคชที่ปนเปื้อนยังคงอยู่
  • การเริ่มปะทุ: ขั้นเผยแพร่

    • เมื่อ 2026-05-11 19:15 UTC Manuel merge PR #7369 ทำให้เกิด main push และ workflow run 25613093674 ของ release.yml เริ่มที่ 19:15:44 ก่อนจะล้มเหลว
    • เมื่อ 2026-05-11 19:20:39 UTC npm registry ได้รับการ publish ของ @tanstack/history@1.161.9 และ sibling package อีก 41 แพ็กเกจ
    • โดยรวมมีการเผยแพร่ประมาณ 84 เวอร์ชัน ครอบคลุม 42 แพ็กเกจ แต่ในวินาทีที่ระบุนี้มองเห็นได้ประมาณครึ่งหนึ่ง ส่วนที่เหลือถูกเผยแพร่ใน run ที่สอง
    • การยืนยันตัวตนในการ publish เกิดขึ้นผ่าน OIDC trusted-publisher binding สำหรับ TanStack/router release.yml@refs/heads/main แต่ไม่ได้เกิดจาก step Publish Packages ของ workflow ที่ถูกข้ามไปเพราะการทดสอบล้มเหลว
    • ผู้เผยแพร่ตัวจริงคือ malware ที่ทำงานระหว่างขั้นทดสอบ/cleanup ซึ่ง mint OIDC token ด้วยสิทธิ์ id-token: write แล้ว POST ไปยัง registry.npmjs.org โดยตรง
    • เมื่อ 2026-05-11 19:20:47 UTC run 25613093674 จบลงด้วยสถานะ failure
    • เมื่อ 2026-05-11 19:16 UTC Manuel merge PR #7382 ทำให้เกิด main push ครั้งที่สอง และ workflow run 25691781302 เริ่มที่ 19:16:22
    • run ที่สองก็ restore แคชที่ปนเปื้อนเดียวกัน และเมื่อ 2026-05-11 19:26:14 UTC ก็มีการเผยแพร่เวอร์ชันชุดที่สองต่อแพ็กเกจ เช่น @tanstack/history@1.161.12 ผ่านกลไก OIDC เดียวกัน
    • เมื่อ 2026-05-11 19:26:20 UTC run 25691781302 ก็จบลงด้วยสถานะ failure เช่นกัน
  • การตรวจพบและการตอบสนอง

    • ราว 2026-05-11 19:50 UTC นักวิจัยภายนอก carlini เปิดอีชู #7383 พร้อม fingerprint ของ optionalDependencies อันตรายและรายชื่อแพ็กเกจ
    • รายชื่อเริ่มต้นมี 14 จากทั้งหมด 42 แพ็กเกจ และนักวิจัยได้แจ้งไปยัง npm security โดยตรงด้วย
    • ราว 2026-05-11 20:00 UTC Manuel ยืนยันเหตุการณ์ใน #7383 และเริ่มการตอบสนอง
    • ราว 2026-05-11 20:10 UTC Manuel ถอดสิทธิ์ push บน GitHub ของสมาชิกทีมคนอื่น ๆ เพื่อป้องกันกรณีที่เครื่องผู้ใช้ถูกเจาะ
    • ราว 2026-05-11 20:30 UTC Tanner ส่งรายการ IOC ทั้งหมดและคำขอให้ลบ tarball ฝั่ง registry ไปยัง security@npmjs.com และส่งรายงาน malware อย่างเป็นทางการผ่าน npm
    • ราว 2026-05-11 21:00 UTC หลังสแกน @tanstack/* ทั้ง 295 แพ็กเกจ จึงยืนยันขอบเขตได้ว่าได้รับผลกระทบ 42 แพ็กเกจ 84 เวอร์ชัน
    • Tanner เริ่มทำ npm deprecation ให้กับแพ็กเกจที่ได้รับผลกระทบทั้ง 84 รายการ และ @tan_stack กับผู้ดูแลได้แจ้งเตือนสาธารณะบน Twitter/X, LinkedIn และ Bluesky
    • เมื่อ 2026-05-11 21:30 UTC มีการระบุเวกเตอร์ cache poisoning ของ pull_request_target ใน bundle-size.yml และ fork zblgg/configuration
    • รายการแคชของ GitHub repository ภายใต้ TanStack/* ทั้งหมดถูกลบผ่าน API
    • hardening PR ถูก merge แล้ว โดยมีการปรับโครงสร้าง bundle-size.yml, เพิ่ม repository_owner guard และตรึง ref ของ third-party action ไว้ที่ SHA
    • มีการเผยแพร่ GitHub Security Advisory อย่างเป็นทางการ และมีการขอ CVE

สาเหตุรากฐาน

  • การผสมกันของช่องโหว่สามจุด

    • การโจมตีนี้ต้องอาศัยช่องโหว่ทั้งสามอย่างร่วมกัน และแค่เพียงอย่างใดอย่างหนึ่งไม่เพียงพอ
    • โค้ดจาก fork PR ข้ามไปยัง cache ของ base repository, cache ของ base repository ข้ามไปยัง runtime ของ release workflow, และ runtime ของ release workflow นำไปสู่สิทธิ์เขียนบน npm registry ทำให้แต่ละช่องโหว่เชื่อมขอบเขตความเชื่อถือของกันและกัน
  • แพตเทิร์น pull_request_target แบบ “Pwn Request”

    • bundle-size.yml ทำงานกับ fork PR ด้วย pull_request_target และภายใต้ trigger context นั้นได้ checkout PR merge ref ของ fork แล้วจึงรัน build
    • โครงสร้างหลักมีดังนี้
    on:
      pull_request_target:
        paths: ['packages/**', 'benchmarks/**']
    
    jobs:
      benchmark-pr:
        steps:
          - uses: actions/checkout@v6.0.2
            with:
              ref: refs/pull/${{ github.event.pull_request.number }}/merge # fork's merged code
    
          - uses: TanStack/config/.github/setup@main # transitively calls actions/cache@v5
    
          - run: pnpm nx run @benchmarks/bundle-size:build # executes fork-controlled code
    
    • ผู้เขียน workflow พยายามแยก job comment-pr กับ job benchmark-pr ออกจากกันเพื่อแยกขอบเขตความเชื่อถือ และในคอมเมนต์ของ YAML ก็ระบุเจตนาว่าต้องการคง benchmark-pr ให้เป็น “untrusted with read-only permissions”
    • แต่ post-job save ของ actions/cache@v5 ไม่สามารถถูกปิดกั้นด้วย permissions: ได้ และการเขียน cache ใช้ token ภายใน runner ไม่ใช่ workflow GITHUB_TOKEN
    • ดังนั้นการตั้งค่า permissions: contents: read จึงไม่สามารถป้องกันการแก้ไข cache ได้
    • ขอบเขตของ cache อยู่ในระดับ repository และ pull_request_target run ที่ใช้ขอบเขต cache ของ base repository จะแชร์ร่วมกับการ push ไปยัง main
    • PR ที่รันอยู่ในขอบเขต cache ของ base repository จึงสามารถทำให้ cache entry ที่ production workflow ของ main จะมา restore ในภายหลังปนเปื้อนได้
  • การทำให้ GitHub Actions cache ปนเปื้อน

    • vite_setup.mjs ที่เป็นอันตรายถูกออกแบบให้เขียนข้อมูลให้ตรงกับ pnpm-store key ที่ release.yml workflow ปกติจะคำนวณและเรียกใช้
    • key เป้าหมายอยู่ในรูปแบบ Linux-pnpm-store-${hashFiles('**/pnpm-lock.yaml')}
    • เมื่อ job benchmark-pr สิ้นสุดลง post-step ของ actions/cache@v5 จะบันทึก pnpm store ที่ปนเปื้อนด้วย key นี้อย่างพอดี
    • หลังจากนั้นเมื่อ release.yml ทำงานจากการ push ไปยัง main step Setup Tools ก็จะ restore entry ที่ปนเปื้อนดังกล่าวตามแผน
    • การโจมตีประเภทนี้อยู่ในตระกูล GitHub Actions cache poisoning ที่ Adnan Khan บันทึกไว้ในปี 2024 และไม่ใช่บั๊กที่จำกัดเฉพาะ TanStack แต่เป็นประเด็นด้านการออกแบบของ GitHub Actions ที่ต้องมีการป้องกันอย่างตั้งใจ
  • การดึง OIDC token จากหน่วยความจำของ runner

    • release.yml ประกาศ id-token: write อย่างถูกต้องเพราะจำเป็นต่อ npm OIDC trusted publishing
    • เมื่อ pnpm store ที่ปนเปื้อนถูก restore ลงบน runner ก็จะมี binary ที่ผู้โจมตีควบคุมอยู่บนดิสก์และถูกเรียกใช้ใน step ของ build
    • binary ดังกล่าวค้นหาโปรเซส GitHub Actions Runner.Worker ผ่าน /proc/*/cmdline แล้วอ่าน /proc/<pid>/maps และ /proc/<pid>/mem เพื่อ dump หน่วยความจำของ worker
    • จากนั้นจึงดึง OIDC token ที่ runner สร้างแบบ lazy mint ภายใต้การตั้งค่า id-token: write ออกมาจากหน่วยความจำ
    • ด้วย token ที่ดึงมาได้ ผู้โจมตีจะยืนยันตัวตนเพื่อส่งคำขอ POST ไปยัง registry.npmjs.org โดยตรง ทำให้ข้าม step Publish Packages ของ workflow ไปทั้งหมด
    • วิธีดึงจากหน่วยความจำนี้เป็นวิธีเดียวกับที่ใช้ในการ compromise ของ tj-actions/changed-files เมื่อเดือนมีนาคม 2025 และมีการใช้ Python script เดียวกันที่มี attribution comment รวมอยู่ด้วย
    • ผู้โจมตีไม่ได้คิดค้นเทคนิคใหม่ แต่เป็นการนำงานวิจัยสาธารณะที่มีอยู่มาประกอบเข้าด้วยกัน
  • เหตุผลที่แต่ละองค์ประกอบเพียงลำพังไม่เพียงพอ

    • ตัว pull_request_target เองยังสามารถใช้กับงานที่เชื่อถือได้ เช่น label หรือ comment
    • การทำ cache poisoning ภายใน dependency ที่ถูก compromise ไปแล้วเพียงอย่างเดียว ยังต้องมีช่องทางสำหรับ publish แยกต่างหาก
    • การดึง OIDC token เพียงอย่างเดียว ยังต้องอาศัยการรันโค้ดบน runner อยู่ก่อนแล้ว

การตรวจจับและ IOC

  • เส้นทางการตรวจจับ

    • การตรวจจับไม่ได้เกิดขึ้นจากภายใน แต่เกิดจากภายนอก
    • carlini เปิด issue #7383 พร้อมให้การวิเคราะห์ทางเทคนิคทั้งหมดภายในราว 20 นาทีหลังมีการ publish
    • Tanner ได้รับโทรศัพท์จาก Socket.dev เพื่อยืนยันสถานการณ์ทันทีหลังเริ่ม war room
  • fingerprint สำหรับ downstream maintainer และเครื่องมือความปลอดภัย

    • ใน manifest ของแพ็กเกจ @tanstack/* รายการ optionalDependencies ต่อไปนี้คือ IOC สำคัญ
    "optionalDependencies": {
      "@tanstack/setup": "github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c"
    }
    
    • IOC ระดับไฟล์คือ router_init.js ที่ root ของ package ซึ่งมีขนาดประมาณ 2.3MB และไม่ได้รวมอยู่ใน "files"
    • cache key คือ Linux-pnpm-store-6f9233a50def742c09fde54f56553d6b449a535adf87d4083690539f49ae4da11
    • URL ของ payload ระยะที่ 2 คือ https://litter.catbox.moe/h8nc9u.js, https://litter.catbox.moe/7rrc6l.mjs
    • เครือข่ายที่ใช้ในการรั่วไหลของข้อมูลคือ filev2.getsession.org, seed{1,2,3}.getsession.org
    • identity ของ commit ที่ปลอมแปลงคือ claude <claude@users.noreply.github.com> ซึ่งไม่ใช่ Anthropic Claude จริง แต่เป็น GitHub no-reply email ที่ถูกปลอมขึ้น
    • บัญชีผู้โจมตีจริงคือ zblgg id 127806521, voicproducoes id 269549300
    • fork ของผู้โจมตีคือ github.com/zblgg/configuration ซึ่งเป็นการเปลี่ยนชื่อจาก fork ของ TanStack/router เพื่อหลบเลี่ยงการค้นหา
    • orphan payload commit ภายในเครือข่าย fork คือ 79ac49eedf774dd4b0cfa308722bc463cfe5885c
    • workflow run ที่ใช้ publish ที่เป็นอันตรายคือ github.com/TanStack/router/actions/runs/25613093674 attempt 4 และ github.com/TanStack/router/actions/runs/25691781302

บทเรียน

  • สิ่งที่ทำได้ดี

    • นักวิจัยภายนอกตรวจพบเหตุการณ์ภายในประมาณ 20 นาทีหลังเกิดเหตุ และรายงานพร้อมรายละเอียดทางเทคนิคทั้งหมด
    • ทีมผู้ดูแลประสานงานกันได้ทันทีข้ามหลายเขตเวลา
    • ชุมชนด้านการตรวจจับได้รูปแบบ IOC ที่เปิดเผยต่อสาธารณะและชัดเจนภายในไม่กี่ชั่วโมง
  • สิ่งที่ควรปรับปรุง

    • ไม่มีการ alerting ภายใน และทราบเรื่องการ compromise จากบุคคลที่สาม
    • จำเป็นต้องมีการ monitoring การ publish ของตนเอง และมีแผนจะทำงานร่วมกับบริษัทวิจัยด้านความปลอดภัยของ ecosystem ที่สามารถตรวจจับปัญหาแบบนี้ได้รวดเร็วยิ่งขึ้น พร้อมลด feedback loop ให้สั้นลง
    • workflow pull_request_target เป็นรูปแบบที่ทราบกันมานานแล้วว่าอันตราย แต่ไม่ได้รับการ audit
    • floating ref ของ third-party action อย่าง @v6.0.2, @main สร้าง supply-chain risk อย่างต่อเนื่องโดยไม่เกี่ยวกับเหตุการณ์นี้โดยตรง
    • เนื่องจากนโยบายของ npm ที่ว่า “หากมี dependent จะ unpublish ไม่ได้” จึงไม่สามารถ unpublish ได้ในแพ็กเกจที่ได้รับผลกระทบเกือบทั้งหมด
    • ต้องพึ่งพา npm security ในการลบ tarball ฝั่ง registry และทำให้มีเวลาเพิ่มอีกหลายชั่วโมงที่ tarball อันตรายยังคงอยู่ในสถานะที่ติดตั้งได้
    • รายชื่อ maintainer 7 คนใน npm scope หมายความว่าเกิด credential-theft target แยกกัน 7 เป้าหมายสำหรับ blast radius เดียวกัน
    • OIDC trusted-publisher binding ไม่มีการ review รายการ publish แต่ละครั้ง และเมื่อกำหนดไว้แล้ว code path ใดก็ตามใน workflow ก็สามารถ mint token ที่ใช้ publish ได้
    • ทางเลือกที่ต้องการคือย้ายไปใช้ classic token ระยะสั้นที่มีการ review แบบ manual หรือเพิ่ม provenance-source-verification เพื่อตรวจจับการ publish จาก workflow step ที่ไม่คาดคิด
  • สิ่งที่โชคดี

    • ผู้โจมตีเลือก payload ที่ทำให้การทดสอบพัง จึงทำให้ขั้นตอน publish ปกติถูกข้ามไป และไม่มีการสร้าง tarball ที่ดูสะอาดกว่านี้
    • ด้วยเหตุนี้ การโจมตีจึงแสดงออกอย่างชัดเจนพอที่จะถูกตรวจพบได้อย่างรวดเร็ว
    • หากเป็นผู้โจมตีที่ระมัดระวังกว่านี้และไม่ทำให้การทดสอบพัง ก็อาจ publish ต่อไปอย่างเงียบ ๆ ได้อีกหลายชั่วโมง
    • ผู้โจมตีนำ memory-dump script แบบเปิดเผยที่มี attribution comment กลับมาใช้ซ้ำ และไม่ได้เขียนโค้ดใหม่ ทำให้การจับคู่ IOC เร็วขึ้น

คำถามที่ยังคงค้างอยู่

  • ต้องยืนยันว่า step Setup Tools ใน bundle-size.yml ได้เรียก actions/cache@v5 จริงหรือไม่
  • ต้องตรวจสอบโดยอ่าน post-job log ของหนึ่งใน pull_request_target run สำหรับ PR #7378 โดยตัวอย่าง run id คือ 25666610798
  • ต้องตรวจสอบว่าใน PR head commit แรกสุดก่อนจะหายไปจาก force-push มีอะไรอยู่บ้าง ซึ่งอาจยังเหลืออยู่ใน GitHub reflog
  • ต้องยืนยันว่า malicious commit เข้าไปอยู่ใน git object store ของ fork ด้วยวิธี git push โดยตรง หรือถูกสร้างผ่าน GitHub web UI ซึ่งจะทิ้ง audit-log entry ไว้
  • ต้องตรวจสอบว่า voicproducoes เป็นบัญชีจริงหรือเป็น sock puppet โดยเทียบกับประวัติกิจกรรม
  • ต้องตรวจสอบว่า npm cache ที่ดูเหมือนเป็น entry linux-npm-store-* ซ้ำกัน 6 รายการ ถูกทำให้ปนเปื้อนด้วยหรือไม่ และถูกใช้งานจริงหรือไม่
  • ต้องตรวจสอบว่าการโจมตีจำเป็นต้องใช้ Nx Cloud หรือไม่ หรือเพียง GitHub Actions cache ก็เพียงพอให้ทำงานได้
  • ต้องตรวจสอบว่าสามารถระบุ fork อื่นใน TanStack/router fork network ที่มี orphan payload commit ได้หรือไม่
  • หาก fork อื่นยัง host commit ดังกล่าวอยู่ การเข้าถึงแบบ github:tanstack/router#79ac49ee... จะยังคงใช้ได้และทำให้การ cleanup ยากขึ้น
  • repo อื่นของ TanStack เช่น router, query, table, form, virtual จำเป็นต้อง audit ว่าใช้รูปแบบ bundle-size.yml ในลักษณะเดียวกันหรือไม่
  • ต้องขอข้อมูลจาก npm support ว่ามีผู้ใช้จำนวนเท่าใดที่ดาวน์โหลดเวอร์ชันที่ได้รับผลกระทบจริงในช่วง publish window
  • ต้องตรวจสอบว่าเครื่องของ maintainer ทั้ง 7 คนถูก compromise แยกกันด้วยหรือไม่
  • แม้ malicious publish จะไม่ได้ใช้ maintainer npm token แต่เครื่องของ maintainer อาจเป็นเป้าหมายลำดับที่สองของ self-propagation logic

เอกสารอ้างอิง

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

 
GN⁺ 2 시간 전
ความเห็นจาก Hacker News
  • ต้องระวังเมื่อเพิกถอนโทเค็น ดูเหมือนว่าเพย์โหลดจะติดตั้ง dead-man's switch ไว้ที่ ~/.local/bin/gh-token-monitor.sh และลงทะเบียนเป็นบริการผู้ใช้ systemd บน Linux และเป็น LaunchAgent com.user.gh-token-monitor บน macOS
    มันจะคอย poll api.github.com/user ทุก 60 วินาทีด้วยโทเค็นที่ขโมยมา และถ้าโทเค็นถูกเพิกถอนจนได้ HTTP 40x ก็จะสั่ง rm -rf ~/
    https://github.com/TanStack/router/issues/7383#issuecomment-...

    • ในทางปฏิบัติ ถ้าติดตั้ง มัลแวร์ ไปแล้ว สุดท้ายก็ต้องล้างเครื่องใหม่ทั้งหมดอยู่ดี
    • น่าตกใจมาก เหมือนสถานการณ์แบบ การทำลายล้างร่วมกันอย่างแน่นอน
      อีก 5 ปีข้างหน้า โลกซอฟต์แวร์น่าจะดุเดือดมาก และ ระบบ air-gap น่าจะยิ่งสำคัญขึ้นมาก
    • จริง ๆ ก็ควรตั้งค่า แบ็กอัป ไว้อยู่แล้วเสมอ แต่ถ้าเหตุการณ์นี้ทำให้คนเริ่มมีแบ็กอัปกันมากขึ้น ก็ยังถือว่าเป็นเรื่องดีอยู่บ้าง
  • แพ็กเกจ npm @mistralai/mistralai ก็ถูกโจมตีเป็นส่วนหนึ่งของ เวิร์ม นี้ด้วย
    https://github.com/mistralai/client-ts/issues/217
    ตอนนี้ถูกถอดออกจาก npm registry แล้ว

  • น่าเสียดาย แต่นี่ดูเป็นหลักฐานว่า Trusted Publishing เพียงอย่างเดียวยังไม่พอสำหรับการ deploy จาก CI อย่างปลอดภัย ถ้าผู้โจมตีอยู่ใน CI pipeline หรือขโมยสิทธิ์ผู้ดูแล repository ได้ ก็ deploy ได้ง่ายมาก
    นี่ไม่ใช่ข้อมูลใหม่ และ Trusted Publishing ก็ไม่ได้ถูกออกแบบมาเพื่อรับประกันเรื่องนี้อยู่แล้ว แต่เมื่อย้ายจากการ deploy แบบ local พร้อมการยืนยันตัวตนสองขั้นตอนไปเป็น Trusted Publishing ก็จะเกิดเส้นทางโจมตีผ่านการเจาะ CI แบบนี้ขึ้นมา เท่ากับปัจจัยที่สองซึ่งเคยช่วยกัน npm publish ตอนทำงานจากเครื่อง local หายไป
    จากสถานการณ์ตอนนี้ ดูเหมือนว่าผู้โจมตีจะยึด CI/CD pipeline ได้ และเพราะ npm publish ไม่มีปัจจัยที่สอง จึงขโมย OIDC token ไปใช้ deploy ต่อได้ ที่น่าสนใจแต่เป็นอีกประเด็นหนึ่งคือ ตัวงาน deploy เองล้มเหลว แต่เพย์โหลดใน malicious commit เหมือนจะใช้ OIDC token ของ workflow เพื่อ deploy ตัวเองได้
    สิ่งที่ต้องการคือยังคงโมเดล Trusted Publisher ที่ไม่มี long-term token ไว้ แต่ให้การ deploy จาก CI ยังมีปัจจัยที่สองจากภายนอก GitHub อยู่ด้วย นั่นคือควรมี การ deploy แบบเป็นขั้นตอน ที่ต้องมีคนไปยืนยันฝั่ง npm ด้วยการยืนยันตัวตนสองขั้นตอนเพื่อเลื่อนอาร์ติแฟกต์ให้เป็นสถานะเผยแพร่จริง
    ถ้าการ deploy ทำได้ภายใน trust model ของ GitHub อย่างเดียว ใครก็ตามที่ขโมยโทเค็นผู้ดูแล repository หรือฝังโค้ดอันตรายใน pipeline ได้ ก็จะ deploy สำเร็จได้ง่ายมาก แต่ถ้ามีปัจจัยที่สองจริง ๆ ที่อยู่นอกบริบทของ GitHub อย่างน้อยพวกเขาจะทำลาย repository หรือฝังโค้ดอันตรายได้ แต่จะเผยแพร่ขึ้น registry ไม่ได้หากไม่มีปัจจัยที่สองนั้น

    • ฉันมีแพ็กเกจระดับกึ่งนิยมตัวหนึ่ง และยังคงใช้ การ deploy แบบ local พร้อมการยืนยันตัวตนสองขั้นตอน อยู่ Trusted Publishing ดูซับซ้อนเกินไปและเหมือนโดนแฮ็กตลอด จนสงสัยว่ามันซับซ้อนเกินกว่าจะเดินระบบอย่างปลอดภัยหรือเปล่า และอาจต้องออกแบบใหม่ตั้งแต่ต้น
    • ฉันยังคิดว่า Trusted Publishing เป็นการพัฒนาที่ดีมาก แต่ไอเดียให้มีปัจจัยที่สองตอนทำเครื่องหมาย release ว่าเผยแพร่จริงนั้นดีมาก แบบนี้จะทำให้เวิร์ม CI แบบนี้ทำงานได้ยากมาก
    • อยากได้ การเซ็นแบบแตะยืนยัน ด้วยอะไรอย่าง YubiKey แนวคิดที่ว่าให้คลาวด์จัดการข้อมูลรับรองแทนเราดูเหมือนเป็นความผิดพลาดตั้งแต่แรก
    • บล็อกของ astral เพิ่งโชว์วิธีตั้ง release gate ขณะใช้ Trusted Publishing อยู่ นั่นคือใส่การอนุมัติด้วยมือเข้าไปใน release workflow น่าเสียดายที่เอกสาร Trusted Publishing ของ NPM/PyPI/Rubygems ไม่พูดถึงความเป็นไปได้นี้เลย และก็ไม่ได้ให้มาเป็นค่าเริ่มต้นด้วย
    • ฉันไม่เคยเข้าใจเลยว่าทำไมคนถึงบอกว่า Trusted Publishing สร้างความแตกต่างอะไรได้มากสำหรับ การโจมตี supply chain แบบนี้
  • บทวิเคราะห์หลังเหตุการณ์: https://tanstack.com/blog/npm-supply-chain-compromise-postmo...

    • ขอบคุณ TanStack สำหรับบทวิเคราะห์หลังเหตุการณ์ แต่ในมุมของระบบนิเวศ npm ทั้งหมด ดูเหมือนประเด็น ความปลอดภัย ยังเป็นความกังวลที่ดำเนินอยู่ไม่ใช่หรือ
      อยากรู้ว่ามีหลักฐานหรือไม่ว่าซับแพ็กเกจที่อาจดึงหรือรวมแพ็กเกจ TanStack เข้าไปนั้นปลอดภัยแล้ว
  • สคริปต์ postinstall ร้ายแรงมาก ทุกคนควรใช้ pnpm
    มันไร้สาระมากที่ commit “กำพร้า” ซึ่งถูก push ไปยัง FORK จะไปก่อเรื่องแบบนี้ใน npm client ได้ ฉันคิดว่า GitHub ก็ต้องรับผิดชอบหนักเหมือนกัน โครงสร้างที่ทำให้ commit จาก malicious fork เข้าถึงได้ด้วย URI ที่แยกไม่ออกจาก repository ปกติผ่าน shared object store ของ GitHub นี่บ้ามาก

    • ถ้าคุณรันแอปด้วย dependency ที่อัปเดตแล้ว โค้ดนั้นก็จะถูกรันอยู่ดี จะเป็น root หรือ non-root ก็ไม่สำคัญนัก เพราะสิ่งสำคัญต่าง ๆ เข้าถึงได้ด้วย สิทธิ์ของผู้ใช้ ที่รันแอปพลิเคชันอยู่
    • ไม่เข้าใจว่านี่ไม่ใช่ เหตุขัดข้องระดับ P0 ของ GitHub ได้อย่างไร มีใครอธิบายได้ไหม?
      ตอนอ่านครั้งแรก ฉันคิดว่าเขาใช้คำว่า “fork” ผิด และจริง ๆ หมายถึง branch ใน repository ทางการ เพราะไม่คิดว่าเรื่องแบบนี้จะเป็นจริงได้ โอ้โห
  • https://tanstack.com/blog/npm-supply-chain-compromise-postmo...
    TanStack เพิ่งเผยแพร่ บทวิเคราะห์หลังเหตุการณ์ ของกรณีนี้

  • เป็นการเตือนให้ตั้งค่าสภาพแวดล้อม npm ให้ปลอดภัย
    https://gajus.com/blog/3-pnpm-settings-to-protect-yourself-f...
    แค่ตั้งค่าไม่กี่อย่างก็ลดปัญหาใหญ่ได้มาก

    • ใน npm v11 ขึ้นไปมี allow-git=none ด้วย: https://github.blog/changelog/2026-02-18-npm-bulk-trusted-pu...
    • ฉันสงสัยว่าบทความนี้จะผิดเรื่อง minimum release age ของ npm หรือเปล่า 1) ชื่อการตั้งค่าคือ min-release-age 2) ด้วยเหตุผลบางอย่างมันถูกทำเป็นหน่วยวันแทนนาที: https://docs.npmjs.com/cli/v11/using-npm/config#min-release-...
      ฉันคิดว่าพื้นที่ของตัวจัดการ dependency มันแตกย่อยแบบไม่จำเป็นสุด ๆ
    • การอ้างว่าตั้งอายุขั้นต่ำไว้ 7 วันแล้วจะ “ไม่มีวัน” เจอ ช่องโหว่ supply chain ของ npm นั้นเกินจริงไปมาก
    • dependency ทุกตัวควรถูก ตรึงเวอร์ชัน
      ถ้าการพึ่งพาเวอร์ชันของแพ็กเกจยังเป็น ^1.0.0 หรือถึงขั้น "*" ก็ไม่ต้องอ่านต่อแล้ว ให้รีบตรึงเป็นเวอร์ชันที่ปลอดภัยทันที
  • ฉันรีบทำอันนี้ด้วย Claude เพื่อช่วยลดการแพร่กระจาย แน่นอนว่าควรตรวจสอบเองอีกที แต่มันจะสแกนว่ามี แพ็กเกจที่ถูกโจมตี ตามที่พูดถึงอยู่ในเครื่องหรือไม่: https://github.com/PaulSinghDev/tanstack-shai-hulud-fix

  • ดูเหมือนตอนนี้เรามาถึงจุดที่ทุกคนควรรันแต่ละโปรเจ็กต์ใน VM แยกกัน แล้ว
    เมื่อดูจากช่องโหว่ยกระดับสิทธิ์บนเครื่อง local ช่วงหลัง ๆ จะเห็นว่า Docker อย่างเดียวไม่พอแน่นอน และแต่แรก container ก็ไม่ได้ถูกออกแบบให้เป็นขอบเขตความปลอดภัยหลักอยู่แล้ว

    • Devcontainers เป็นรูปแบบ “สภาพแวดล้อมพัฒนาแบบแยก” ที่คนรู้จักกันมากที่สุด แต่ไม่ใช่ VM เต็มรูปแบบ และป้องกันเหตุการณ์นี้ได้ไม่หมด เพราะข้อมูลรับรอง GitHub ถูกใส่เข้าคอนเทนเนอร์โดยอัตโนมัติ
      ถ้ามีบริการคลาวด์อื่นที่ต้องเข้าถึงจากในคอนเทนเนอร์ ตัวขโมยข้อมูลรับรองนี้ก็จะเอาส่วนนั้นไปด้วย ถึงอย่างนั้นมันก็ยังช่วยลด ขอบเขตความเสียหาย ได้ จึงอย่างน้อยก็ยังดีขึ้น
    • QubesOS เดินมาถูกทางแล้ว คุณเริ่มอยากมี VM หลายตัวบน root และมี ชั้นความปลอดภัยหลายชั้น
    • ถ้าจะใช้คอนเทนเนอร์จริง ๆ ก็อาจทำให้แต่ละคอนเทนเนอร์มี VM ของตัวเองไปเลย ช่วงไม่กี่สัปดาห์ที่ผ่านมาอยู่ได้ค่อนข้างสบายเพราะไม่ได้รัน Kubernetes service แบบสุ่ม แต่รันทุกอย่างบน VM ทั้งหมด
    • โชคดีที่โปรเจ็กต์ที่ใช้ระบบนิเวศภาษาที่ปลอดภัยกว่าอย่าง C และ C++ ไม่เจอปัญหาแบบนี้ :-)
  • โอ้โห นี่ก็เป็นอีกแพ็กเกจใหญ่หนึ่ง ฉันเลยเอาประกาศเพื่อสาธารณประโยชน์ที่เคยโพสต์หลัง Axios และ LiteLLM ถูกโจมตีมาโพสต์ซ้ำ เพราะเรื่อง lifecycle script ก็ใช้ได้กับกรณีนี้เหมือนกัน
    ตอนนี้ npm/bun/pnpm/uv รองรับการตั้งค่าอายุขั้นต่ำของ release ของแพ็กเกจแล้วทั้งหมด ฉันใส่ ignore-scripts=true ไว้ใน ~/.npmrc ด้วย และจากการวิเคราะห์ ดูเหมือนแค่นี้ก็ช่วยบรรเทาช่องโหว่นี้ได้ bun และ pnpm จะไม่รัน lifecycle script โดยค่าเริ่มต้น
    วิธีตั้งอายุขั้นต่ำของ release เป็น 7 วันแบบ global มีดังนี้
    ~/.config/uv/uv.toml
    exclude-newer = "7 days"
    ~/.npmrc
    min-release-age=7 # days
    ignore-scripts=true
    ~/Library/Preferences/pnpm/rc
    minimum-release-age=10080 # minutes
    ~/.bunfig.toml
    [install]
    minimumReleaseAge = 604800 # seconds
    หากต้อง override การตั้งค่า global ก็ใช้ CLI flag ได้
    npm install --min-release-age 0
    pnpm add --minimum-release-age 0
    uv add --exclude-newer "0 days"
    bun add --minimum-release-age 0
    ขอเสริมอีกอย่างหนึ่ง ฉันเห็นความกังวลว่าถ้านำการหน่วงเวลา dependency มาใช้ในวงกว้าง จะทำให้การตรวจพบช่องโหว่ช้าลง หรือการหน่วงเวลา dependency เป็นเหมือนการอาศัยคนอื่นตรวจให้ฟรี ซึ่งฉันไม่เห็นด้วย สิ่งที่เราแลกด้วยการหน่วงเวลา dependency คือ ความชอบด้านเวลา และย่อมมีคนที่มีความชอบด้านเวลาสูงกว่าฉันอยู่เสมอ
    0: https://news.ycombinator.com/item?id=47582220
    1: https://news.ycombinator.com/item?id=47513932

    • เห็นด้วย โชคดีที่ฉันเปิดการตั้งค่าเหล่านี้ไว้ตั้งแต่เดือนมีนาคม ก่อนคลื่นสองครั้งล่าสุดจะมา อีกอย่างหนึ่งคือต้อง commit lockfile ไว้ใน repository และระวังเวลาเพิ่ม dependency ใหม่
      ถ้าอยากหลีกเลี่ยงการเปลี่ยนแปลงที่ไม่คาดคิด ให้ใช้ pnpm install --frozen-lockfile และต้องจำไว้ว่าถ้าไม่ได้ตั้ง min-release-age คุณก็อาจดึงแพ็กเกจที่ได้รับผลกระทบเข้ามาผ่าน dependency ทางอ้อมได้ด้วย ถ้าเป็นไปได้ควรตรึงเวอร์ชันของ package manager ไว้ด้วย