Dependency Confusion

Reading time: 9 minutes

tip

Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Ucz się i ćwicz Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Wsparcie dla HackTricks

Basic Information

Dependency Confusion (znane również jako ataki substytucyjne) występuje, gdy menedżer pakietów rozwiązuje nazwę zależności z niezamierzonego, mniej zaufanego rejestru/źródła (zwykle publicznego rejestru) zamiast zamierzonego prywatnego/wewnętrznego. Zwykle prowadzi to do zainstalowania pakietu kontrolowanego przez atakującego.

Typowe przyczyny:

  • Typosquatting/błędne pisanie: Importowanie reqests zamiast requests (rozwiązuje z publicznego rejestru).
  • Nieistniejący/porzucony wewnętrzny pakiet: Importowanie company-logging, który już nie istnieje wewnętrznie, więc resolver szuka w publicznych rejestrach i znajduje pakiet atakującego.
  • Preferencje wersji w różnych rejestrach: Importowanie wewnętrznego company-requests, podczas gdy resolver ma również prawo do zapytań w publicznych rejestrach i preferuje „najlepszą” / nowszą wersję opublikowaną publicznie przez atakującego.

Kluczowa idea: Jeśli resolver może zobaczyć wiele rejestrów dla tej samej nazwy pakietu i ma prawo wybrać „najlepszego” kandydata globalnie, jesteś narażony, chyba że ograniczysz rozwiązywanie.

Exploitation

warning

W każdym przypadku atakujący musi tylko opublikować złośliwy pakiet o tej samej nazwie, co zależność, którą twoja kompilacja rozwiązuje z publicznego rejestru. Hooki w czasie instalacji (np. skrypty npm) lub ścieżki kodu w czasie importu często dają możliwość wykonania kodu.

Misspelled & Inexistent

Jeśli twój projekt odnosi się do biblioteki, która nie jest dostępna w prywatnym rejestrze, a twoje narzędzia wracają do publicznego rejestru, atakujący może umieścić złośliwy pakiet o tej nazwie w publicznym rejestrze. Twoje maszyny wykonawcze/CI/deweloperskie pobiorą i wykonają go.

Unspecified Version / “Best-version” selection across indexes

Programiści często pozostawiają wersje nieprzypisane lub pozwalają na szerokie zakresy. Gdy resolver jest skonfigurowany z zarówno wewnętrznymi, jak i publicznymi indeksami, może wybrać najnowszą wersję niezależnie od źródła. Dla wewnętrznych nazw, takich jak requests-company, jeśli wewnętrzny indeks ma 1.0.1, ale atakujący publikuje 1.0.2 w publicznym rejestrze i twój resolver bierze pod uwagę obie, pakiet publiczny może wygrać.

AWS Fix

Ta podatność została znaleziona w AWS CodeArtifact (przeczytaj szczegóły w tym poście na blogu). AWS dodało kontrole, aby oznaczyć zależności/źródła jako wewnętrzne lub zewnętrzne, aby klient nie pobierał „wewnętrznych” nazw z upstream publicznych rejestrów.

Finding Vulnerable Libraries

W oryginalnym poście na temat zamieszania z zależnościami autor szukał tysięcy ujawnionych manifestów (np. package.json, requirements.txt, pliki blokady), aby wywnioskować wewnętrzne nazwy pakietów, a następnie publikował pakiety o wyższych wersjach w publicznych rejestrach.

Practical Attacker Playbook (for red teams in authorized tests)

  • Enumerate names:
  • Grep repos i konfiguracje CI w poszukiwaniu manifestów/pliki blokady i wewnętrznych przestrzeni nazw.
  • Szukaj prefiksów specyficznych dla organizacji (np. @company/*, company-*, wewnętrzne groupIds, wzory ID NuGet, prywatne ścieżki modułów dla Go itp.).
  • Sprawdź dostępność w publicznych rejestrach:
  • Jeśli nazwa nie jest zarejestrowana publicznie, zarejestruj ją; jeśli istnieje, spróbuj przejąć subzależności, celując w wewnętrzne nazwy przejściowe.
  • Publish with precedence:
  • Wybierz semver, który „wygrywa” (np. bardzo wysoką wersję) lub odpowiada zasadom resolvera.
  • Dołącz minimalne wykonanie w czasie instalacji, gdzie to możliwe (np. skrypty npm preinstall/install/postinstall). Dla Pythona preferuj ścieżki wykonania w czasie importu, ponieważ koła zazwyczaj nie wykonują dowolnego kodu podczas instalacji.
  • Exfil control:
  • Upewnij się, że wychodzące połączenia są dozwolone z CI do twojego kontrolowanego punktu końcowego; w przeciwnym razie użyj zapytań DNS lub komunikatów o błędach jako kanału bocznego do udowodnienia wykonania kodu.

caution

Zawsze uzyskuj pisemną autoryzację, używaj unikalnych nazw pakietów/wersji dla zaangażowania i natychmiast unpublikuj lub skoordynuj czyszczenie po zakończeniu testów.

Defender Playbook (what actually prevents confusion)

Strategie na wysokim poziomie, które działają w różnych ekosystemach:

  • Używaj unikalnych wewnętrznych przestrzeni nazw i przypisuj je do jednego rejestru.
  • Unikaj mieszania poziomów zaufania w czasie rozwiązywania. Preferuj jeden wewnętrzny rejestr, który pośredniczy w zatwierdzonych publicznych pakietach, zamiast dawać menedżerom pakietów zarówno wewnętrzne, jak i publiczne punkty końcowe.
  • Dla menedżerów, którzy to wspierają, mapuj pakiety do konkretnych źródeł (brak globalnej „najlepszej wersji” w różnych rejestrach).
  • Pin and lock:
  • Używaj plików blokady, które rejestrują rozwiązane adresy URL rejestru (npm/yarn/pnpm) lub używaj przypinania haszy/atestacji (pip --require-hashes, weryfikacja zależności Gradle).
  • Zablokuj publiczne zaplecze dla wewnętrznych nazw na poziomie rejestru/sieci.
  • Rezerwuj swoje wewnętrzne nazwy w publicznych rejestrach, gdy to możliwe, aby zapobiec przyszłemu squat.

Ecosystem Notes and Secure Config Snippets

Poniżej znajdują się pragmatyczne, minimalne konfiguracje, aby zredukować lub wyeliminować zamieszanie z zależnościami. Preferuj egzekwowanie ich w CI i środowiskach deweloperskich.

JavaScript/TypeScript (npm, Yarn, pnpm)

  • Używaj pakietów z zakresem dla całego kodu wewnętrznego i przypisz zakres do swojego prywatnego rejestru.
  • Utrzymuj instalacje niezmienne w CI (plik blokady npm, yarn install --immutable).

.npmrc (poziom projektu)

# Bind internal scope to private registry; do not allow public fallback for @company/*
@company:registry=https://registry.corp.example/npm/
# Always authenticate to the private registry
//registry.corp.example/npm/:_authToken=${NPM_TOKEN}
strict-ssl=true

package.json (dla wewnętrznego pakietu)

{
"name": "@company/api-client",
"version": "1.2.3",
"private": false,
"publishConfig": {
"registry": "https://registry.corp.example/npm/",
"access": "restricted"
}
}

Yarn Berry (.yarnrc.yml)

npmScopes:
company:
npmRegistryServer: "https://registry.corp.example/npm/"
npmAlwaysAuth: true
# CI should fail if lockfile would change
enableImmutableInstalls: true

Operational tips:

  • Publikuj tylko wewnętrzne pakiety w obrębie zakresu @company.
  • Dla pakietów zewnętrznych, zezwól na publiczny rejestr za pośrednictwem swojego prywatnego proxy/lustra, a nie bezpośrednio od klientów.
  • Rozważ włączenie pochodzenia pakietów npm dla publicznych pakietów, które publikujesz, aby zwiększyć śledzenie (samo w sobie nie zapobiega pomieszaniu).

Python (pip / Poetry)

Core rule: Nie używaj --extra-index-url, aby mieszać poziomy zaufania. Albo:

  • Udostępnij jeden wewnętrzny indeks, który proxy i buforuje zatwierdzone pakiety PyPI, lub
  • Użyj jawnego wyboru indeksu i przypinania haszy.

pip.conf

[global]
index-url = https://pypi.corp.example/simple
# Disallow source distributions when possible
only-binary = :all:
# Lock with hashes generated via pip-tools
require-hashes = true

Wygeneruj zhaszowane wymagania za pomocą pip-tools:

# From pyproject.toml or requirements.in
pip-compile --generate-hashes -o requirements.txt
pip install --require-hashes -r requirements.txt

Jeśli musisz uzyskać dostęp do publicznego PyPI, zrób to przez swój wewnętrzny proxy i utrzymuj tam wyraźną listę dozwolonych źródeł. Unikaj --extra-index-url w CI.

.NET (NuGet)

Użyj mapowania źródeł pakietów, aby powiązać wzorce identyfikatorów pakietów z wyraźnymi źródłami i zapobiec rozwiązywaniu z nieoczekiwanych źródeł.

nuget.config

<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<add key="corp" value="https://nuget.corp.example/v3/index.json" />
</packageSources>
<packageSourceMapping>
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
<packageSource key="corp">
<package pattern="Company.*" />
<package pattern="Internal.Utilities" />
</packageSource>
</packageSourceMapping>
</configuration>

Java (Maven/Gradle)

Maven settings.xml (lustro wszystko do wewnętrznego; zabroń repozytoriów ad-hoc w POM-ach za pomocą Enforcer):

<settings>
<mirrors>
<mirror>
<id>internal-mirror</id>
<mirrorOf>*</mirrorOf>
<url>https://maven.corp.example/repository/group</url>
</mirror>
</mirrors>
</settings>

Dodaj Enforcer, aby zablokować repozytoria zadeklarowane w POM-ach i wymusić użycie swojego lustra:

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.6.1</version>
<executions>
<execution>
<id>enforce-no-repositories</id>
<goals><goal>enforce</goal></goals>
<configuration>
<rules>
<requireNoRepositories />
</rules>
</configuration>
</execution>
</executions>
</plugin>

Gradle: Centralizuj i zablokuj zależności.

  • Wymuś repozytoria tylko w settings.gradle(.kts):
dependencyResolutionManagement {
repositoriesMode = RepositoriesMode.FAIL_ON_PROJECT_REPOS
repositories {
maven { url = uri("https://maven.corp.example/repository/group") }
}
}
  • Włącz weryfikację zależności (sumy kontrolne/podpisy) i zatwierdź gradle/verification-metadata.xml.

Go Modules

Skonfiguruj prywatne moduły, aby publiczny proxy i baza danych sum kontrolnych nie były używane dla nich.

# Use corporate proxy first, then public proxy as fallback
export GOPROXY=https://goproxy.corp.example,https://proxy.golang.org
# Mark private paths to skip proxy and checksum db
export GOPRIVATE=*.corp.example.com,github.com/your-org/*
export GONOSUMDB=*.corp.example.com,github.com/your-org/*

Rust (Cargo)

Zastąp crates.io zatwierdzonym wewnętrznym lustrem lub katalogiem dostawcy dla kompilacji; nie zezwalaj na dowolne publiczne zapasowe źródło.

.cargo/config.toml

[source.crates-io]
replace-with = "corp-mirror"

[source.corp-mirror]
registry = "https://crates-mirror.corp.example/index"

Aby opublikować, bądź dokładny z --registry i ogranicz dane uwierzytelniające do docelowego rejestru.

Ruby (Bundler)

Użyj bloków źródłowych i wyłącz multisource Gemfiles, aby gemy pochodziły tylko z zamierzonego repozytorium.

Gemfile

source "https://gems.corp.example"

source "https://rubygems.org" do
gem "rails"
gem "pg"
end

source "https://gems.corp.example" do
gem "company-logging"
end

Wymuś na poziomie konfiguracji:

bundle config set disable_multisource true

CI/CD i Kontrole Rejestru, Które Pomagają

  • Prywatny rejestr jako pojedynczy punkt dostępu:
  • Użyj Artifactory/Nexus/CodeArtifact/GitHub Packages/Azure Artifacts jako jedynego punktu, do którego mogą uzyskać dostęp deweloperzy/CI.
  • Wprowadź zasady blokowania/zezwalania, aby wewnętrzne przestrzenie nazw nigdy nie były rozwiązywane z publicznych źródeł upstream.
  • Pliki blokad są niemutowalne w CI:
  • npm: zatwierdź package-lock.json, użyj npm ci.
  • Yarn: zatwierdź yarn.lock, użyj yarn install --immutable.
  • Python: zatwierdź haszowany requirements.txt, wymuś --require-hashes.
  • Gradle: zatwierdź verification-metadata.xml i niepowodzenie w przypadku nieznanych artefaktów.
  • Kontrola wychodzącego ruchu: zablokuj bezpośredni dostęp z CI do publicznych rejestrów, z wyjątkiem zatwierdzonego proxy.
  • Rezerwacja nazw: wstępnie zarejestruj swoje wewnętrzne nazwy/przestrzenie nazw w publicznych rejestrach, gdzie to możliwe.
  • Pochodzenie pakietów / zaświadczenia: podczas publikowania publicznych pakietów włącz pochodzenie/zaświadczenia, aby uczynić manipulacje bardziej wykrywalnymi w dalszej kolejności.

Odniesienia

tip

Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Ucz się i ćwicz Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Wsparcie dla HackTricks