Extension VS Code tưởng lành, hóa ra ăn trộm cả GitHub
Tôi có đâu đó bốn mươi extension VS Code đang cài. Chưa kiểm tra cái nào bao giờ. Cài xong là dùng. Không hỏi extension kia lấy quyền gì, kết nối đi đâu, đọc file nào. Cứ xanh xanh là bấm Install.
Vụ GitHub bị breach vừa rồi nhắc tôi cái thói quen đó không hề rẻ như mình nghĩ.
Chuyện gì xảy ra
Một kỹ sư của GitHub cài một phiên bản giả mạo của Nx Console - extension quản lý monorepo JavaScript khá phổ biến. Nhóm tấn công TeamPCP đã nhét mã độc vào phiên bản này và phân phối thành công qua VS Code Marketplace chính thức.
Khi extension chạy trên máy kỹ sư đó, nó lấy cắp thông tin xác thực. Kết quả GitHub công nhận: khoảng 3.800 repo nội bộ bị truy cập và đánh cắp.
GitHub phát biểu thẳng thắn: “Hôm qua chúng tôi phát hiện và kiềm chế một vụ xâm nhập thiết bị nhân viên liên quan đến extension VS Code bị nhiễm độc. Chúng tôi đã xóa phiên bản độc hại, cô lập endpoint, và bắt đầu phản ứng sự cố ngay lập tức.”
Đánh giá hiện tại của họ là không có dữ liệu khách hàng nào bị ảnh hưởng - chỉ là repo nội bộ của GitHub. Còn TeamPCP thì đăng dữ liệu lên diễn đàn tội phạm Breached ba ngày sau với mức giá khá thú vị.
“Như thường lệ, đây không phải tiền chuộc. Chúng tôi không quan tâm đến việc tống tiền Github. 1 người mua và chúng tôi sẽ xóa dữ liệu ở đầu mình… Không quan tâm đến dưới 50k.”
Không phải tiền chuộc. Ừ nhỉ.
--> // making it invisible to querySelectorAll. // // `data-cfasync="false"` keeps this rescue script executable even when // Rocket Loader is active. It rescues module scripts via two strategies: // 1. Query the DOM for type$="-module" + src (covers case A) // 2. Regex-parse the raw HTML for commented-out script tags (covers case B) // Dynamically-created scripts bypass Rocket Loader entirely. (function () { if (window.__markdyRescue) return; window.__markdyRescue = true; var rescued = false; function rescueModuleScripts() { if (rescued) return; rescued = true; var srcs = []; // Strategy 1: Rocket Loader kept the tag in DOM but changed the type. // type="module" → type="{uuid}-module" (still has src attribute) document.querySelectorAll('script[type$="-module"][src]').forEach(function (s) { srcs.push(s.src); }); // Strategy 2: Rocket Loader COMMENTED OUT the script tag entirely: // // These are invisible to querySelectorAll, so we parse the raw HTML. // We handle both attribute orderings (type-first or src-first). var html = document.documentElement.innerHTML; var reSrcFirst = //g; var reTypeFirst = //g; var m; while ((m = reSrcFirst.exec(html)) !== null) { srcs.push(m[1]); } while ((m = reTypeFirst.exec(html)) !== null) { srcs.push(m[1]); } // Re-inject each found src as a real module script. // Deduplicate first, then inject. Dynamically-created scripts bypass // Rocket Loader entirely. Modules with the same URL are only executed // once by the browser (cached), so re-injecting already-running scripts // is safe. var seen = {}; srcs.forEach(function (src) { if (seen[src]) return; seen[src] = true; var fix = document.createElement('script'); fix.type = 'module'; fix.src = src; fix.setAttribute('data-cfasync', 'false'); document.head.appendChild(fix); }); } // Rescue when user clicks the placeholder (fallback if autoplay failed). document.addEventListener('click', function (e) { var t = e.target; if (t && typeof t.closest === 'function' && t.closest('.markdy-placeholder')) { rescueModuleScripts(); } }); // Rescue automatically after a short delay for autoplay. // Only fires if initAll() never ran (no data-markdy-init on any root). setTimeout(function () { if (document.querySelector('.markdy-root:not([data-markdy-init])')) { rescueModuleScripts(); } }, 1500); }());Tại sao bài này leo lên 1.000 upvote trong một ngày
Chuỗi tấn công gọn không tưởng:
Extension hợp lệ (Nx Console)
↓
TeamPCP nhiễm độc phiên bản mới
↓
Đẩy lên VS Code Marketplace chính thức
↓
Kỹ sư GitHub cài update
↓
Token/thông tin xác thực bị đánh cắp
↓
~3.800 repo nội bộ bị truy cập
↓
Dữ liệu rao bán 50.000 đô trên Breached forum
Không phải ai cũng sốc vì nó xảy ra - mà vì nó xảy ra với GitHub, cái nền tảng lưu code của cả thế giới, thông qua công cụ mà chính các kỹ sư của họ dùng hằng ngày.
TeamPCP cũng không phải tay mới. Họ đã liên quan đến vụ tấn công chuỗi cung ứng TanStack npm và chiến dịch “Mini Shai-Hulud” - cái vụ sau cũng làm hai nhân viên của OpenAI dính chưởng. Đây không phải tấn công ngẫu nhiên, mà là nhắm thẳng vào công cụ dành cho lập trình viên một cách có hệ thống.
Có một bài học cũ nhưng giờ hiển nhiên hơn bao giờ hết: chuỗi cung ứng không chỉ là các thư viện trong package.json. Nó là mọi thứ chạy trong môi trường phát triển của bạn.
Cộng đồng HN đang nổi giận về chuyện gì
Comment đáng nhớ nhất đến từ fg137: “Sự thiếu hụt bảo mật của VSCode vẫn luôn gây kinh ngạc. Người ta đã đề nghị sandbox hóa extension nhiều năm nay với rất ít tiến triển.”
Thread kéo dài. Extension VS Code chạy dưới dạng tiến trình Node.js với toàn quyền trên hệ thống - không giới hạn khả năng, không cần xin phép, không sandbox. Bạn cài extension; nó có thể đọc file, kết nối mạng, chạy tiến trình con.
notnullorvoid nói gọn: “Tôi thực sự mong điều này thúc đẩy Microsoft thêm hệ thống phân quyền tường minh cho extension VS Code.”
Lý thì đúng, nhưng đây là cuộc chiến đã cũ. Issue trên GitHub của VS Code về sandbox extension mở từ năm 2018. Vẫn mở. Firefox cũng từng mất nhiều năm tranh luận trước khi có Manifest v3. Thay đổi thường chờ đến khi ai đó đủ lớn bị đau.
GitHub đủ lớn.
Góc nhìn khác từ thread: làm sao extension ăn cắp thông tin mà không bị hệ thống mạng phát hiện? Câu trả lời khá buồn: không cần phải qua hàng rào khó nào cả. IDE của bạn có quyền truy cập mạng ra ngoài. Luôn như vậy. Và bạn thường không monitor những gì nó gửi đi.
gus_ đặt câu hỏi khổ hơn: “Làm sao họ exfiltrate thông tin mà không ai phát hiện? Developer đó dùng OS gì? Họ đang dùng biện pháp bảo mật gì?”
Không ai trả lời vì không ai biết, và GitHub cũng chưa nói.
Có nên kiểm tra lại extension ngay bây giờ không?
| Câu hỏi | Câu trả lời thực tế |
|---|---|
| Có nên xem lại danh sách extension đang cài? | Có, ngay bây giờ |
| Có nên xóa extension không dùng thường xuyên? | Có |
| Extension phổ biến thì an toàn không? | Không - Nx Console có hàng nghìn lượt cài |
| Marketplace chính thức đáng tin không? | Đáng tin hơn nguồn khác, nhưng không đồng nghĩa an toàn |
| Có nên ghim phiên bản extension? | Ít nhất là check trước khi update |
| VS Code sẽ sớm có sandbox không? | Issue mở từ 2018. Bạn tự tính. |
Điều thực sự giúp ích là đối xử với IDE của mình như một máy chủ sản xuất: không phải thứ gì có nút Install màu xanh cũng cần chạy trên máy bạn với mọi quyền truy cập.
Câu mà GitHub không muốn phải viết ra
Trong phát biểu của GitHub có câu này: “Tuyên bố của kẻ tấn công về ~3.800 kho lưu trữ là nhất quán về hướng với cuộc điều tra của chúng tôi cho đến nay.”
Nhất quán về hướng. Tức dịch là: chúng tôi tin họ, chỉ là chưa đếm xong.
Cái irony sâu hơn: GitHub là nền tảng được thiết kế để quản lý và bảo vệ mã nguồn cho hàng triệu tổ chức. Họ có Dependabot, quét bí mật, code review, branch protection, audit logs. Cả bộ máy hạ tầng bảo mật đó - và breach đến từ trình quản lý extension trên laptop của một kỹ sư.
Bảo mật là một dây xích. Và mắt xích yếu nhất thường là cái bạn không nhìn thấy.
Thảo luận trên Hacker News · Nguồn: bleepingcomputer.com · Đăng bởi Timofeibu
Hoang Yell
Một nhà phát triển phần mềm và là người kể chuyện kỹ thuật. Tôi đọc Hacker News mỗi ngày và kể lại những câu chuyện hay nhất ở đây — bằng tiếng Việt và tiếng Anh, cho người tò mò nhưng không có thời gian.