1 điểm bởi GN⁺ 3 giờ trước | 1 bình luận | Chia sẻ qua WhatsApp
  • github.dev nhận OAuth token được chuyển từ github.com để thực hiện xem tệp, PR và commit trong VSCode trên trình duyệt; token này không bị giới hạn vào một kho lưu trữ cụ thể nên có thể đọc và ghi trên toàn bộ các kho mà người dùng có quyền truy cập
  • VSCode webview được cô lập bằng iframe vscode-webview://..., nhưng để hỗ trợ UX phím tắt, keydown trong webview được chuyển tới cửa sổ chính qua thông điệp did-keydown, khiến script không đáng tin cậy có thể gửi sự kiện như thể là thao tác gõ phím của người dùng
  • Không thể nhập văn bản tùy ý do HTML <input>, nhưng có thể kết hợp phím tắt mặc định Ctrl+Shift+A, thông báo cài extension được đề xuất, local workspace extensions và keybinding tùy chỉnh để thực thi lệnh cài extension
  • PoC chạy JavaScript trong ô markdown của Jupyter notebook để chấp nhận cài extension được đề xuất, sau đó cài extension đã chọn bằng keybinding mới, rồi hiển thị token GitHub API và danh sách kho riêng tư
  • VSCode desktop cũng có cùng lỗ hổng, nhưng kẻ tấn công phải dụ nạn nhân clone kho và mở notebook; người dùng github.dev nên xóa dữ liệu trang để hộp thoại xác nhận ban đầu xuất hiện lại như một biện pháp phòng vệ

Tổng quan lỗ hổng

  • github.dev mở một VSCode nhẹ chạy trong trình duyệt khi đổi URL kho GitHub có thể truy cập từ github.com sang github.dev hoặc nhấp vào mục menu
  • VSCode trên trình duyệt này có thể xem tệp trong kho, mở cả kho riêng tư, đồng thời gửi PR và tạo commit
  • github.com POST OAuth token sang github.dev để tương tác với GitHub thay mặt người dùng, và token này không bị giới hạn trong kho cụ thể mà người dùng đang thao tác
  • Kẻ tấn công có thể đánh cắp GitHub token có quyền đọc/ghi chỉ bằng một cú nhấp vào liên kết, bao gồm cả quyền với các kho riêng tư

Cô lập Webview và vấn đề chuyển tiếp phím bấm

  • VSCode webviews cô lập việc thực thi JavaScript bằng cách dùng <iframe> với origin khác với cửa sổ VSCode chính
  • Đầu ra của Jupyter notebook được render trong <iframe> có origin vscode-webview://..., còn cửa sổ Electron chính dùng origin vscode-file://...
  • Nhờ cơ chế cô lập này, ngay cả khi notebook dùng hiển thị HTML hoặc widget tương tác dựa trên JavaScript, nó vẫn không thể gọi Node.js API của Electron hay VSCode API từ bên trong iframe
  • Các tính năng cần cửa sổ chính và webview phối hợp, như Markdown preview, sẽ trao đổi thông điệp qua Window.postMessage() API
  • VSCode chuyển tiếp sự kiện did-keydown từ webview sang cửa sổ chính để các phím tắt như Ctrl+Shift+P vẫn hoạt động ngay cả khi đang focus trong webview
  • Script không đáng tin cậy bên trong webview có thể tự phát sinh sự kiện keydown và giả mạo như người dùng đang nhấn phím

Chuỗi tấn công

  • Có thể mở command palette bằng Ctrl+Shift+P, nhưng vì command palette dùng HTML <input>, cách nhập chuỗi tùy ý sẽ không hiệu quả
  • Tuy vậy, vẫn có thể tận dụng các thao tác được xử lý qua keydown như phím mũi tên và Enter, cùng với bộ phím tắt mặc định của VSCode
  • Ctrl+Shift+A là keybinding mặc định cho “Notifications: Accept Notification Primary Action”, tức bấm nút chính của thông báo VSCode gần nhất
  • Nếu thêm extension được đề xuất vào .vscode/extensions.json, VSCode sẽ hiện thông báo cài đặt; tuy nhiên từ VSCode 1.97, hệ thống publisher trust sẽ hiện thêm hộp thoại tin cậy khi cài extension từ publisher mới
  • Có thể dùng Tab để di chuyển giữa các nút, nhưng xử lý Enter của nút “Trust Publisher & Install” lại gắn với keydown của chính nút đó, nên rất khó hoàn tất cài đặt chỉ bằng đường này
  • local workspace extensions cho phép cài trực tiếp extension nằm trong .vscode/extensions bên trong workspace đã được tin cậy, và github.dev/web workspace luôn ở trạng thái trusted
  • Nếu cố chạy trực tiếp local workspace extension, extension worker sẽ mong đợi extension đến từ vscode-cdn.net, dẫn tới lỗi CSP
  • Thay vào đó, có thể thêm keybinding tùy chỉnh vào package.json của local workspace extension, rồi dùng keybinding đó để gọi workbench.extensions.installExtension với ngữ cảnh skipPublisherTrust

Hoạt động của PoC và tác động

  • Cấu hình cần thiết là một kho có Jupyter notebooklocal workspace extension
  • Ô markdown trong notebook có thể chạy JavaScript thông qua thuộc tính onerror của ảnh
  • Payload chờ đến khi VSCode hiện thông báo cài extension được đề xuất, rồi gửi sự kiện Ctrl+Shift+A để chấp nhận hành động chính của thông báo
  • Sau đó, nó tiếp tục chờ đến khi extension được cài và kích hoạt, đồng thời keybinding tùy chỉnh sẵn sàng, rồi gửi sự kiện Ctrl+F1 để kích hoạt việc cài extension đã chọn
  • Extension được cài trong PoC sẽ lấy GitHub API token, truy vấn https://api.github.com/user/repos để lấy các kho riêng tư mà người dùng có thể truy cập, rồi hiển thị token và danh sách kho trong một hộp thông tin
  • Sau khi chạy PoC, cần xóa dữ liệu github.dev hoặc gỡ extension PoC; nếu không gỡ, nó sẽ theo người dùng trên mọi trang github.dev
  • VSCode desktop cũng bị cùng lỗ hổng, nhưng kẻ tấn công phải dụ nạn nhân clone kho và mở notebook có chứa payload script trong webview
  • Nếu webview mà nạn nhân mở còn có một XSS khác, trên desktop điều này thực tế có thể dẫn tới thực thi mã từ xa toàn phần

Phòng vệ và các yếu tố giảm thiểu

  • Nếu trước đây chưa từng dùng github.dev, khi vào trang sẽ có một hộp thoại cần nhấp xác nhận, tạo cơ hội thoát khỏi trang tấn công
  • Xóa cookie và dữ liệu trang cục bộ của github.dev có thể khiến hộp thoại ban đầu này xuất hiện lại
  • Trên Chrome, có thể nhấn biểu tượng ở thanh URL rồi vào Cookies and site data > Manage on-device site data để xóa dữ liệu của các domain liên quan
  • Nếu đã vượt qua hộp thoại github.dev trước đó và chưa xóa local storage của trình duyệt, github.dev không có cơ chế bảo vệ như CSRF token, nên bất kỳ liên kết nào trên Internet cũng có thể chuyển hướng sang trang tấn công
  • VSCode không chỉ dựa vào cô lập iframe mà còn dùng Content Security Policy nghiêm ngặt và DOMPurify
  • Vì Markdown preview trong trang extension dùng script-src 'none' để chặn thực thi JavaScript tùy ý, nên tác động lớn hơn là RCE 1-click trên desktop chỉ từ một liên kết extension đã bị ngăn chặn

Bối cảnh công bố và mốc thời gian

  • MSRC trước đây từng âm thầm sửa các báo cáo lỗi VSCode mà không ghi công và đánh dấu là không có tác động bảo mật
  • Báo cáo gần đây về lỗi VSCode XSS của Starlabs cũng bị đánh dấu là không đủ điều kiện và mức độ nghiêm trọng thấp
  • Có thể đội VSCode cần thêm thời gian để cân bằng giữa UI/UX và bảo mật, nhưng tác giả chọn công bố toàn bộ vì không nên xem thời gian và công sức của nhà nghiên cứu bảo mật là điều hiển nhiên
  • Một giờ trước khi đăng bài vào ngày 2 tháng 6 năm 2026, kế hoạch công bố đã được gửi tới đầu mối liên hệ sẵn có bên bảo mật GitHub
  • Lỗ hổng được công khai trong cùng ngày và cũng đã được đăng trên VSCode issue tracker

1 bình luận

 
Ý kiến trên Hacker News
  • Đây là một bài tổng hợp hay, và nhìn rộng ra thì điều đáng tiếc là chính việc trình soạn thảo VSCode nhúng trên web lại đăng nhập sẵn vào GitHub
    Bỏ qua chuyện có phòng thủ nhiều lớp hay không, chính nguyên tội đó đã tạo ra bề mặt tấn công rất lớn. Nó gần giống như để sẵn một GitHub API token có toàn quyền ở dạng văn bản thuần trên máy trạm để một gói NPM độc hại có thể tìm thấy
    Lý tưởng nhất là IDE trên trình duyệt nên chạy bằng phạm vi quyền tạm thời theo từng kho lưu trữ hoặc token chỉ cho phép pull/push với đúng kho đó, và hoàn toàn không có phiên web github.com. Nếu cần toàn bộ giao diện web GitHub thì quay lại github.com, còn github.dev nên là một dịch vụ dành cho một kho lưu trữ duy nhất thì có vẻ hợp lý hơn
    Tuy vậy, điều đó sẽ gây bất tiện cho người dùng, khó triển khai, và có lẽ còn vướng những giả định đã ăn sâu trong toàn bộ hệ công cụ github.dev từ lâu

    • Codespaces thực tế làm như vậy. Token chỉ có quyền đọc/ghi với kho lưu trữ của Codespace đang được kích hoạt [1]
      github.dev cũng nên nghiêm túc cân nhắc cách này
      [1] https://orca.security/resources/blog/hacking-github-codespac...
    • Vấn đề gói NPM độc hại có lẽ sẽ ngày càng nghiêm trọng hơn. Gần đây tôi thấy các công cụ thực thi AI như OpenCode tải về các gói npm tùy ý ở nền và rải chúng khắp thư mục home lẫn thư mục dự án mà không hề báo hay hỏi người dùng
      Tệ hơn nữa là có vẻ ngay cả các lập trình viên cũng chẳng mấy bận tâm
    • Khi mở kho lưu trữ của chính mình thì việc đang đăng nhập còn chấp nhận được, nhưng khi mở kho của tài khoản khác thì tuyệt đối không nên như vậy. Và phím tắt bàn phím của webview cũng cần được sửa để chỉ cho phép các key binding vô hại và không lan truyền tới bất kỳ trình xử lý keydown nào
      Trên desktop, có lẽ tốt hơn là để Electron tự chặn trực tiếp và loại bỏ tính năng này; còn trên web thì nên tắt theo mặc định
    • Dùng SSH key và GitHub deploy key thì có thể tạo ra thứ gì đó khá tương tự. Tôi không dám khẳng định về độ an toàn, nhưng tôi chưa từng cấu hình GitHub theo kiểu cho phép truy cập mọi kho lưu trữ
      Tôi cũng không rõ các dịch vụ host Git khác có tính năng tương tự hay không
    • Có lẽ nên cho phép pull từ kho đó, nhưng thay vì push bằng token thì chỉ push vào một vùng staging nơi người dùng có thể tự thực hiện lần push cuối cùng
      Thành thật mà nói, các tác nhân LLM cũng nên làm như vậy. Để LLM tự push trực tiếp có vẻ rất liều lĩnh
  • Lý do cuộc tấn công này đặc biệt rắc rối là vì extension của VSCode chạy với cùng mức độ tin cậy như chính trình soạn thảo, trong khi phần lớn lập trình viên cài hàng chục extension mà không xem xét quyền hạn
    Nếu một extension độc hại hoặc bị chiếm quyền âm thầm rò rỉ GitHub token thì rất khó phát hiện nếu không có giám sát mạng, và đó là lý do nên chạy extension trong các profile bị cô lập

    • Ngay cả khi có giám sát mạng thì nếu dữ liệu bị tuồn về chính GitHub cũng rất khó ngăn chặn. Đặc biệt là nếu không có SSL interception và danh sách URL được phép cực kỳ nghiêm ngặt
      Cách tốt nhất là rời khỏi GitHub, chuyển sang GitLab/Forgejo nội bộ tự host và chặn GitHub hoàn toàn
  • Gần đây tôi cũng gặp chuyện tương tự. GitHub token và Cloudflare token của tôi đã bị đánh cắp
    Tôi nghĩ dù có nghiêm túc với bảo mật đến đâu thì nếu đủ thời gian, cuối cùng bạn vẫn sẽ bị dính đòn. Điều tốt nhất là tách biệt mọi thứ và kiểm soát phạm vi thiệt hại
    Đừng tin ai, đừng tin thứ gì, hãy dùng OrbStack, và luôn làm việc với giả định rằng token rồi cũng sẽ bị lộ vào một ngày nào đó
    Luồng công việc của tôi bị gián đoạn hoàn toàn, nhưng may là bên lấy token có vẻ gần giống bot spam hơn. Chúng tạo ra hàng loạt trang spam giả và cố đào tiền mã hóa
    Cảm giác đọng lại lớn nhất là thấy mình bị xâm phạm. Mong mọi người cẩn thận

    • Không biết có phải là các trang như GitHub Pages không. Có phải các kho lưu trữ đã được tạo trong tài khoản của bạn không? Tôi cũng tò mò bạn đã phát hiện token bị lộ như thế nào
  • Đoạn nói rằng khi báo cáo lỗi VSCode cho MSRC thì họ âm thầm sửa luôn là một trải nghiệm tồi tệ đúng kiểu MSRC. Có vẻ họ đã nhận ra rằng các nhà nghiên cứu dù sao cũng sẽ báo miễn phí, nên không thấy lý do gì phải thay đổi

    • MSRC không phải bên trực tiếp sửa lỗi
      Tôi không biết chi tiết cụ thể của trường hợp này, nhưng trước đây tôi từng vận hành chương trình bug bounty qua Bountysource và HackerOne. Đôi khi báo cáo bị chuyển tới đội phát triển trước cả khi đội bảo mật đánh giá xong
      Ở thời điểm đó, lập trình viên có thể âm thầm sửa nó luôn
      Đôi khi là vì lo ngại, dù hợp lý hay không, rằng nếu bị gắn với lỗi bảo mật thì sẽ trông không hay cho bản thân hoặc ảnh hưởng tới cơ hội thăng tiến. Kết quả là đến khi đội bảo mật thử tái hiện lại thì lỗ hổng đã biến mất
      Từ phía MSRC, họ chỉ thấy rằng các bước tái hiện được cung cấp không còn hoạt động nữa. Họ không nhìn thấy lịch sử lỗi nội bộ hay việc ai đó đã vá trước rồi. Vì vậy báo cáo sẽ bị đóng là không hợp lệ, ngay cả khi phát hiện ban đầu là hoàn toàn chính đáng
    • Trong một thời gian dài đó là trạng thái mặc định, nhưng rồi những nhà nghiên cứu bảo mật khó chiều bắt đầu đòi tiền thưởng thay vì danh tiếng
  • Cảm ơn vì đã gần như bỏ công vô điều kiện để cho thấy VS Code cần cải thiện cách phản hồi bảo mật. Bạn hoàn toàn có thể bỏ cuộc, nhưng vẫn tiếp tục giúp đỡ

    • Tôi không định bán hay ém lỗ hổng này. Chỉ là việc tạo một bản chứng minh khái niệm có thể tốn vài giờ, nên nếu nhà cung cấp chỉ lặng lẽ vá lỗi mà không ghi nhận hay cảm ơn gì thì thực sự rất khó chịu
  • Tôi không thật sự hiểu vì sao nhiều lập trình viên hơn lại không thử Neovim
    Có thể là do sở thích, nhưng tôi thích một cấu hình nhỏ gọn, nơi có thể biết được cái gì đang được cài và cái gì đang chạy. Khi trộn lẫn VSCode, IDE trên trình duyệt, extension, đồng bộ, token và plugin ngẫu nhiên, sẽ rất khó biết thứ gì đang truy cập vào thứ gì

    • Tôi đã bỏ VS Code vài năm trước và chuyển sang Neovim. Đó là sau khi tôi phát hiện VS Code tự động cài các gói Python ngẫu nhiên cho những thư viện không có định nghĩa kiểu mặc định
      Đây là tính năng của extension Python chính thức từ Microsoft, và ở nhiều mặt khác thì đó gần như là extension dùng được duy nhất, nhưng nó lại cài định nghĩa kiểu cho phiên bản thư viện khác với phiên bản mà dự án của tôi sử dụng. Nó khiến tôi rất bất an vì trông như đang thản nhiên chạy mã bên thứ ba chưa được kiểm chứng, và dường như cũng không thể tắt bằng cấu hình
      Tôi muốn nói rằng “từ đó tôi không ngoái đầu nhìn lại”, nhưng thành thật mà nói, trong 1~2 năm qua Neovim bắt đầu thường xuyên làm hỏng cấu hình của tôi gần như sau mỗi lần nâng cấp. Tôi cũng đã linh cảm rằng chuyện này sớm muộn gì sẽ xảy ra. Nói cho chính xác thì đã 10 năm trôi qua nhưng nvim vẫn chưa phát hành bản ổn định đầu tiên, nên không thể trách sự bất ổn đó, nhưng đây là điều đáng ghi nhớ
      Tôi đang cân nhắc quay lại Vim thuần. Tôi sẽ mất khá nhiều tính năng tiện lợi, nhưng ít nhất mong là sẽ bớt phải debug các chức năng bị hỏng ngay giữa lúc làm việc
    • Tôi khá thích Helix. Tôi chưa đào sâu vào Neovim, nhưng Helix có khá nhiều tính năng kiểu IDE mà tôi luôn thấy thiếu ở Vim
      Không cần cài cả đống plugin hay dùng thứ như SpaceVim. Bạn có thể thử xem, biết đâu sẽ thích
    • Tôi nhận ra rằng việc khiến người ta thay đổi thói quen phần mềm là khá khó. Có những phím tắt phải học, và lúc đầu nó sẽ cho cảm giác chậm hơn, càng củng cố cảm giác “không tốt hơn”
      Cần thời gian để quen với nvim, nhưng khi đã quen thì nhanh hơn. Dù vậy, điều đó cũng giải thích vì sao nhiều người vẫn ở trong vùng an toàn của mình
  • Việc công khai tiết lộ là làm đúng. Có quá nhiều người bất mãn với MSRC, và giờ chuyện đó bắt đầu tràn ly giống như vụ Nightmare Eclipse
    Nếu những vụ công khai như thế này tích tụ đủ nhiều, có lẽ MSRC sẽ tự nhìn lại và nhận ra chính họ là vấn đề. Có vẻ khả năng đó thấp, nhưng vẫn có thể hy vọng

    • Tôi vẫn không chắc đây có phải là cách tiếp cận tốt nhất không. Có vẻ tác giả đoán rằng nó sẽ bị xếp hạng “thấp” nếu so với các báo cáo XSS trước đây, nên đã không gửi luôn
      Dù vậy, tôi nghĩ ít nhất vẫn nên thử gửi, hoặc thông báo cho họ trước vài ngày rồi mới công khai. Không ai biết trước chuyện gì sẽ xảy ra mà
  • Bài viết rất hay, nhưng phần cuối làm tôi hơi bối rối. Tôi muốn kiểm tra xem mình hiểu đúng không
    Tác giả nói rằng vì hệ thống tin cậy nhà phát hành mới, không thể trực tiếp cài extension độc hại chỉ bằng mẹo dùng phím tắt; có thể lách bằng extension workspace cục bộ không có kiểm tra nhà phát hành, nhưng CSP lại chặn điều đó
    Có vẻ cách giải quyết là cài một extension workspace cục bộ có gán phím tắt “cài extension mà không cần xác minh nhà phát hành”
    Vì vậy tôi muốn hỏi 1) có phải cần hai extension không. Cái đầu tiên là extension cục bộ chỉ để gán phím, còn cái thứ hai là extension độc hại thực sự, thứ mà do CSP nên không cần là local và trên thực tế cũng không thể là local, và 2) CSP chỉ chặn JS của extension local, chứ không chặn package.json hay khả năng thêm phím tắt của nó, đúng không

    • 1 và 2 đều đúng. Hãy xem repo proof-of-concept: https://github.com/ammaraskar/github-dev-token-steal-poc/tre...
      Để thực thi trực tiếp nhất thì bạn có thể thử đưa my-extension/extension.js vào, nhưng CSP sẽ chặn. Tuy nhiên đây là script-src CSP nên nó chỉ chặn script, còn việc nạp package.json thì vẫn được phép. Vì vậy người ta lợi dụng điều đó để thêm key binding
  • Tình hình MSRC thật sự khó tin
    Có thể có tài liệu tốt hơn, nhưng tôi nghĩ video này của The Primeagen là tài liệu nhập môn khá ổn
    https://www.youtube.com/watch?v=9kxx5xp5nTQ

  • Tôi có một góp ý nhỏ với đoạn “Cách duy nhất để cho phép hành vi này là để hai trang web khác nguồn phối hợp với nhau qua API Window.postMessage()
    Cũng có thể giao tiếp bằng cách để iframe hoặc cửa sổ cha thay đổi thuộc tính location.anchor