NIS2/KSC2 starter pack. Czy Twoja firma podlega pod regulację i co z tego wynika? Bezpłatne szkolenie od sekuraka

Konferencja Mega Sekurak Hacking Party w Krakowie – 26-27 października!

Bezpłatne szkolenie: AI dla admina. Top 5 zadań, które zrobisz szybciej

Podatność w webowym VSCode (GitHub) pozwalała przejąć token użytkownika

29 czerwca 2026, 07:35 | Aktualności | 0 komentarzy

Badacz bezpieczeństwa Ammar Askar odkrył podatność w webowym edytorze (Visual Studio Code) udostępnianym w ramach GitHub. Samo otwarcie repozytorium mogło umożliwić atakującemu kradzież tokenu, który pozwala uzyskać dostęp do repozytoriów ofiary.

TLDR:

  • Badacz bezpieczeństwa Ammar Askar odkrył podatność w webowej wersji Visual Studio Code w ramach GitHub, która mogła prowadzić do przejęcia tokenu użytkownika.
  • Atak wykorzystywał mechanizm webview i możliwość przekazywania zdarzeń klawiatury (keydown) z osadzonego notebooka Jupyter do głównego okna edytora.
  • Dzięki odpowiednio przygotowanemu repozytorium atakujący mógł doprowadzić do instalacji złośliwego rozszerzenia VSCode i uzyskać dostęp do tokenu GitHub posiadającego uprawnienia do wszystkich repozytoriów ofiary.
  • Do przeprowadzenia ataku wystarczało otwarcie spreparowanego repozytorium w github.dev.
  • Microsoft opublikował poprawki blokujące wykorzystany wektor ataku dzień po jego publicznym ujawnieniu.

GitHub udostępnia funkcję do łatwej edycji plików w ramach repozytorium. Wystarczy w adresie URL zmienić domenę github.com na github.dev. Wtedy w przeglądarce uruchamia się Visual Studio Code z widokiem danego repozytorium/pliku.

Rys. 1 – przykładowy plik otwarty w webowym edytorze, źródło: github.dev

Webowy edytor ma dostęp do repozytorium na takim samym poziomie, jak użytkownik. Umożliwia przeglądanie wszystkich plików, wysyłanie pull requestów, a także commitów.

Dzieje się tak dlatego, że github.com wysyła w żądaniu POST token OAuth do github.dev, który umożliwia dostęp do zasobów danego użytkownika. Co ważne – token ten nie jest ograniczony do konkretnego repozytorium – pozwala na pełny dostęp do innych repozytoriów (także prywatnych).

Visual Studio Code najczęściej kojarzymy jako aplikację desktopową opartą na Electronie, co oznacza że wykonywanie kodu JavaScript wewnątrz VSC oznaczałoby po prostu wykonanie kodu na urządzeniu użytkownika. Dlatego zastosowano sandboxing, między innymi w postaci webview.

Dla webview zdefiniowany jest inny origin niż dla głównego okna VSCode, aby zapewnić izolację wykonywanego w nich kodu. Metoda ta jest używana m.in. do podglądu Markdown albo edycji notatników Jupyter:

Rys. 2 – renderowanie kodu HTML wewnątrz iframe w Jupyter, źródło: blog.ammaraskar.com

Jak widać źródłem dla elementu iframe jest vscode-webview://…, w przeciwieństwie do głównego okna Electron, którego origin to vscode-file://…. Oznacza to, że nawet jeśli notatnik Jupyter używa JavaScript, nie można skorzystać z integracji Electrona w ramach tego iframe ani wywoływać API VSCode.

To jednak “tylko” statyczna treść. W podglądzie Markdown jest funkcja, która podkreśla aktualnie zaznaczoną linię, a także aktualizuje podgląd na żywo podczas edycji.

Rys. 3 – podgląd Markdown, źródło: blog.ammaraskar.com

Zastosowanie osobnych origin uniemożliwia również głównemu oknu edytora interakcję z DOM w ramce iframe. Jedynym sposobem na komunikację między nimi jest API Window.postMessage(). Ta metoda pozwala przesyłać obiekty JavaScript między różnymi oknami. W przykładzie z podglądem Markdown główne okno edytora wysyła wiadomość:

{
    type: “onDidChangeTextEditorSelection”,
    line: 31
}

Listing 1 – komunikat postMessage, źródło: blog.ammaraskar.com

a kod w webview nasłuchuje tej wiadomości i aktualizuje podkreślenie:

window.addEventListener(‘message’, async event => {
                const data = event.data as ToWebviewMessage.Type;
                switch (data.type) {
                    …
                    case ‘onDidChangeTextEditorSelection’:
                        marker.onDidChangeTextEditorSelection(data.line, documentVersion);
                        return;

Listing 2 – aktualizacja podkreślenia linii, źródło: blog.ammaraskar.com

Webowa wersja Visual Studio Code stosuje podobny mechanizm sandboxingu. Przeglądarka izoluje ramki iframe od głównej strony. Utrudnia to jednak działanie skrótów klawiszowych.

Rys. 4 – webview w VSCode, źródło: blog.ammaraskar.com

Przeglądarka standardowo nie pozwala stronom nasłuchiwać kliknięć klawiszy w ramkach z innego origin – nie chcemy przykładowo, aby strona przechwytywała wprowadzane w ramce dane, a tym bardziej hasła czy numery kart płatniczych.

Aby skróty klawiszowe w VSCode mogły działać także gdy użytkownik kliknie w webview (czyli wejdzie w interakcję z ramką, a nie bezpośrednio stroną), do głównego okna przekazywany jest event keydown. Odpowiada za to ten fragment kodu:

contentWindow.addEventListener(‘keydown’, handleInnerKeydown);
contentWindow.addEventListener(‘keydown’, handleInnerKeydown);

/**
 * @param {KeyboardEvent} e
 */
const handleInnerKeydown = (e) => {
    // …
    hostMessaging.postMessage(‘did-keydown’, {
        key: e.key,
        keyCode: e.keyCode,
        code: e.code,
        shiftKey: e.shiftKey,
        altKey: e.altKey,
        ctrlKey: e.ctrlKey,
        metaKey: e.metaKey,
        repeat: e.repeat
    });
};

Listing 3 – obsługa eventu keydown, źródło: blog.ammaraskar.com

W uproszczeniu możemy powiedzieć, że webview przekazuje ten event do głównego okna VSCode, żeby został on potraktowany tak jak kliknięcie poza obszarem webview.

Teoretycznie skrypt działający wewnątrz webview mógłby symulować działanie użytkownika i wywoływać keydown dla dowolnych klawiszy. W tym celu np. otwierałby widok command palette i uruchamiał niebezpieczne komendy, takie jak instalacja złośliwego rozszerzenia.

W rzeczywistości jednak nie jest to takie proste. Choć złośliwy webview mógłby symulować event keydown, przeglądarka nie przyjmie w taki sposób wpisywanych komend/poleceń. Faktycznie można otworzyć command palette, ale nie ma możliwości wpisania w nim tekstu. Jednak command palette nie jest jedyną opcją.

Visual Studio Code ma zestaw domyślnych skrótów klawiszowych, które reagują bezpośrednio na keydown. Po eksperymentach badacz wybrał akcję “Notifications: Accept Notification Primary Action”. Skrót Ctrl+Shift+A powoduje zatwierdzenie “głównej akcji” z ostatniego powiadomienia.

O jakim powiadomieniu i akcji mowa? Badacz skorzystał z funkcji rekomendowania rozszerzeń przez plik .vscode/extensions.json:

{
  “recommendations”: [
    “atakujacy.dowolna-zlosliwa-wtyczka”
  ]
}

Listing 4 – przykładowy fragment extensions.json

Nie jest to jednak wystarczające, by zainstalować wtyczkę z poziomu webview. VSCode od wersji 1.97 ma system zaufania wydawców – instalacja rozszerzenia od nowego wydawcy pokazuje dodatkowe okno, nawet jeśli klikniemy Install:

Rys. 5 – dodatkowe potwierdzenie, źródło: blog.ammaraskar.com

Można przejść do przycisku potwierdzenia z pomocą Tab, ale symulowanie jego kliknięcia przy użyciu zdarzenia keydown nie będzie możliwa.

Zamiast tego można użyć innej funkcji VSCode: lokalnych rozszerzeń. W zaufanym workspace (a w ramach github.dev wszystkie workspaces są zaufane), można instalować rozszerzenia bezpośrednio z katalogu .vscode/extensions. Wtyczki instalowane w ten sposób omijają sprawdzenie wydawcy. Przy próbie uruchomienia pojawia się jednak błąd Content Security Policy, bo program oczekuje, że rozszerzenia będą pochodziły z vscode-cdn[.]net.

Rys. 6 – błędy CSP, źródło: blog.ammaraskar.com

To jednak uniemożliwia tylko wykonywanie samego kodu wtyczki. Rozszerzenia mogą jeszcze definiować dodatkowe skróty klawiszowe w package.json. Badacz zauważył, że ten plik zostaje obsłużony poprawnie. Wystarczy więc zadeklarować w nim skrót do dowolnej komendy VSCode, np. instalacji rozszerzenia z pominięciem weryfikacji.

“contributes”: {
  “keybindings”: [
    {
      “key”: “ctrl+f1”,
      “command”: “runCommands”,
      “args”: {
        “commands”: [
          {
            “command”: “workbench.extensions.installExtension”,
            “args”: [
              “AmmarTest.hello-ammar-github”,
              {
                “donotSync”: true,
                “context”: {
                  “skipPublisherTrust”: true
                }
              }
            ]
          }
        ]
      }
    }
  ]
}

Listing 5 – package.json ze złośliwym skrótem, źródło: blog.ammaraskar.com

Do przeprowadzenia ataku wykorzystującego tę podatność wystarczy przygotować repozytorium z notatnikiem Jupyter i lokalnym rozszerzeniem. Notatnik musi uruchomić JavaScript, co możemy zrobić przez osadzenie odpowiednio spreparowanego obrazka (znacznika HTML) w Markdown:

<img src=”data:foobar” onerror=”javascript(); goes(); here();”>

Listing 6 – sposób na osadzenie kodu JS w Markdown, źródło: blog.ammaraskar.com

Payload stworzony w celu demonstracji ataku wyglądał tak:

// Wait for VSCode to load and pop open the notification.
await sleep(10 * 1000);

// ctrl+shift+a, accept the primary notification asking if we want to install
// the recommended extension
window.dispatchEvent(
  new KeyboardEvent(“keydown”, {key: “a”, code: “KeyA”, keyCode: 65, 
                                ctrlKey: true, shiftKey: true})
);
// Wait a little for the extension to install…
await sleep(500);

// ctrl+f1, the custom keybind to install the chosen extension.
window.dispatchEvent(
  new KeyboardEvent(“keydown”, {key: “F1”, code: “F1”, keyCode: 112, ctrlKey: true})
);

Listing 7 – payload PoC, źródło: blog.ammaraskar.com

Skrypt najpierw czeka chwilę, aby VSCode zdążyło wyświetlić użytkownikowi komunikat o instalacji rekomendowanego rozszerzenia.

Następnie wysyła event keydown (Ctrl+Shift+A), aby zaakceptować powiadomienie. Później czeka, aż rozszerzenie się zainstaluje i symuluje kliknięcie Ctrl+F1, aby uruchomić własny skrót klawiszowy.

Do wykonania tych działań potrzebne jest tylko otwarcie przez użytkownika złośliwego repozytorium w webowej wersji VSCode na github.dev. W efekcie złośliwa wtyczka może uzyskać dostęp do tokenu GitHub ofiary, co badacz przetestował listując jej repozytoria:

Rys. 7 – PoC: lista repozytoriów, źródło: blog.ammaraskar.com

Ten komunikat służył jedynie celom demonstracji ataku. Po stronie użytkownika równie dobrze mógłby zostać po prostu widok pliku w repozytorium:

Rys. 8 – widok złośliwego repozytorium, źródło: blog.ammaraskar.com

Badacz z uwagi na dotychczasowe problemy ze zgłaszaniem podatności do Microsoft, zdecydował się na publiczne ujawnienie luki.

Przy tego rodzaju podatności trudno o rady w zakresie zabezpieczania się. Możemy napisać, że odradzamy otwieranie zewnętrznych repozytoriów w github.dev, ale… ona do tego właśnie służy. Jedyną sensowną strategią będzie więc otwieranie nieznanych/niezaufanych repozytoriów w oknie incognito, aby użyta instancja VSC nie miała dostępu do naszego tokenu GitHub.

Oś czasu:

  • 2 czerwca 2026 – godzinę przed publikacją tekstu badacz informuje o niej kontakt w zespole GitHub Security
  • 2 czerwca 2026 – badacz ujawnia błąd na swoim blogu i w issue trackerze VSCode
  • 3 czerwca 2026 – Microsoft wprowadza tymczasową poprawkę: dodaje potwierdzenie przy otwieraniu notatników w webowym VSCode i blokuje możliwość pomijania weryfikacji wydawcy wtyczki przez komendy
  • 3 czerwca 2026 – Microsoft wdraża bardziej kompleksową poprawkę ograniczającą eventy keydown w webview notatników

Źródło: blog.ammaraskar.com

~Tymoteusz Jóźwiak

Spodobał Ci się wpis? Podziel się nim ze znajomymi:



Komentarze

Odpowiedz