Dependency Confusion

Reading time: 9 minutes

tip

Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Impara e pratica il hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporta HackTricks

Basic Information

La Dependency Confusion (nota anche come attacchi di sostituzione) si verifica quando un gestore di pacchetti risolve un nome di dipendenza da un registro/sorgente non intenzionato e meno affidabile (di solito un registro pubblico) invece di quello privato/interno previsto. Questo porta tipicamente all'installazione di un pacchetto controllato dall'attaccante.

Cause comuni:

  • Typosquatting/misspelling: Importare reqests invece di requests (risolve da un registro pubblico).
  • Pacchetto interno inesistente/abbandonato: Importare company-logging che non esiste più internamente, quindi il risolutore cerca nei registri pubblici e trova un pacchetto dell'attaccante.
  • Preferenza di versione tra più registri: Importare un company-requests interno mentre il risolutore è autorizzato a interrogare anche registri pubblici e preferisce la versione “migliore”/più recente pubblicata pubblicamente da un attaccante.

Idea chiave: Se il risolutore può vedere più registri per lo stesso nome di pacchetto ed è autorizzato a scegliere il “miglior” candidato a livello globale, sei vulnerabile a meno che tu non limiti la risoluzione.

Exploitation

warning

In tutti i casi, l'attaccante deve solo pubblicare un pacchetto malevolo con lo stesso nome della dipendenza da cui il tuo build si risolve da un registro pubblico. Gli hook di installazione (ad es., script npm) o i percorsi di codice al momento dell'importazione spesso consentono l'esecuzione di codice.

Misspelled & Inexistent

Se il tuo progetto fa riferimento a una libreria che non è disponibile nel registro privato, e il tuo strumento torna a un registro pubblico, un attaccante può seminare un pacchetto malevolo con quel nome nel registro pubblico. I tuoi runner/macchine CI/dev lo recupereranno ed eseguiranno.

Unspecified Version / “Best-version” selection across indexes

Gli sviluppatori spesso lasciano le versioni non fissate o consentono intervalli ampi. Quando un risolutore è configurato con entrambi gli indici interni e pubblici, può selezionare la versione più recente indipendentemente dalla fonte. Per nomi interni come requests-company, se l'indice interno ha 1.0.1 ma un attaccante pubblica 1.0.2 nel registro pubblico e il tuo risolutore considera entrambi, il pacchetto pubblico potrebbe prevalere.

AWS Fix

Questa vulnerabilità è stata trovata in AWS CodeArtifact (leggi i dettagli in questo post del blog). AWS ha aggiunto controlli per contrassegnare le dipendenze/i feed come interni o esterni in modo che il client non recuperi nomi “interni” da registri pubblici upstream.

Finding Vulnerable Libraries

Nel post originale sulla confusione delle dipendenze, l'autore ha cercato migliaia di manifesti esposti (ad es., package.json, requirements.txt, lockfiles) per dedurre i nomi dei pacchetti interni e poi ha pubblicato pacchetti con versioni superiori nei registri pubblici.

Practical Attacker Playbook (for red teams in authorized tests)

  • Enumerare nomi:
  • Grep repos e configurazioni CI per manifesti/file di blocco e spazi dei nomi interni.
  • Cercare prefissi specifici dell'organizzazione (ad es., @company/*, company-*, groupId interni, modelli ID NuGet, percorsi di moduli privati per Go, ecc.).
  • Controllare i registri pubblici per disponibilità:
  • Se il nome non è registrato pubblicamente, registralo; se esiste, tenta il dirottamento della subdipendenza mirato a nomi transitori interni.
  • Pubblica con precedenza:
  • Scegli una semver che “vince” (ad es., una versione molto alta) o che corrisponde alle regole del risolutore.
  • Includi l'esecuzione minima al momento dell'installazione dove applicabile (ad es., script npm preinstall/install/postinstall). Per Python, preferisci i percorsi di esecuzione al momento dell'importazione, poiché i wheel tipicamente non eseguono codice arbitrario all'installazione.
  • Exfil control:
  • Assicurati che l'uscita sia consentita da CI al tuo endpoint controllato; altrimenti utilizza query DNS o messaggi di errore come canale secondario per dimostrare l'esecuzione di codice.

caution

Ottieni sempre un'autorizzazione scritta, utilizza nomi/versioni di pacchetti unici per l'impegno e immediatamente dispubblica o coordina la pulizia quando il test si conclude.

Defender Playbook (what actually prevents confusion)

Strategie ad alto livello che funzionano attraverso gli ecosistemi:

  • Utilizza spazi dei nomi interni unici e collegali a un singolo registro.
  • Evita di mescolare livelli di fiducia al momento della risoluzione. Preferisci un singolo registro interno che proxy i pacchetti pubblici approvati invece di dare ai gestori di pacchetti sia endpoint interni che pubblici.
  • Per i gestori che lo supportano, mappa i pacchetti a fonti specifiche (niente “miglior-versione” globale tra i registri).
  • Fissa e blocca:
  • Utilizza lockfiles che registrano gli URL dei registri risolti (npm/yarn/pnpm) o utilizza il pinning hash/attestazione (pip --require-hashes, verifica delle dipendenze Gradle).
  • Blocca il fallback pubblico per nomi interni a livello di registro/rete.
  • Riserva i tuoi nomi interni nei registri pubblici quando possibile per prevenire futuri squat.

Ecosystem Notes and Secure Config Snippets

Di seguito sono riportate configurazioni pragmatiche e minime per ridurre o eliminare la confusione delle dipendenze. Preferisci far rispettare queste in CI e negli ambienti di sviluppo.

JavaScript/TypeScript (npm, Yarn, pnpm)

  • Utilizza pacchetti scoped per tutto il codice interno e fissa lo scope al tuo registro privato.
  • Mantieni le installazioni immutabili in CI (lockfile npm, yarn install --immutable).

.npmrc (project-level)

# 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 (per pacchetto interno)

{
"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:

  • Pubblica solo pacchetti interni all'interno dello scope @company.
  • Per i pacchetti di terze parti, consenti il registro pubblico tramite il tuo proxy/mirror privato, non direttamente dai client.
  • Considera di abilitare la provenienza dei pacchetti npm per i pacchetti pubblici che pubblichi per aumentare la tracciabilità (non previene di per sé la confusione).

Python (pip / Poetry)

Regola fondamentale: Non usare --extra-index-url per mescolare livelli di fiducia. O:

  • Esporre un singolo indice interno che proxy e memorizza nella cache i pacchetti PyPI approvati, oppure
  • Utilizzare la selezione esplicita dell'indice e il pinning degli hash.

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

Genera requisiti hash con pip-tools:

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

Se devi accedere a PyPI pubblico, fallo tramite il tuo proxy interno e mantieni un allowlist esplicito lì. Evita --extra-index-url in CI.

.NET (NuGet)

Utilizza il Package Source Mapping per collegare i modelli di ID pacchetto a fonti esplicite e prevenire la risoluzione da feed inaspettati.

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 (specchia tutto su interno; vieta i repository ad-hoc nei POM tramite Enforcer):

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

Aggiungi Enforcer per vietare i repository dichiarati nei POM e forzare l'uso del tuo mirror:

<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: Centralizza e blocca le dipendenze.

  • Applica i repository solo in settings.gradle(.kts):
dependencyResolutionManagement {
repositoriesMode = RepositoriesMode.FAIL_ON_PROJECT_REPOS
repositories {
maven { url = uri("https://maven.corp.example/repository/group") }
}
}
  • Abilita la verifica delle dipendenze (checksum/firme) e committa gradle/verification-metadata.xml.

Go Modules

Configura i moduli privati in modo che il proxy pubblico e il DB dei checksum non vengano utilizzati per essi.

# 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)

Sostituisci crates.io con un mirror interno approvato o una directory di fornitori per le build; non consentire un fallback pubblico arbitrario.

.cargo/config.toml

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

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

Per la pubblicazione, sii esplicito con --registry e mantieni le credenziali limitate al registro di destinazione.

Ruby (Bundler)

Usa i blocchi di origine e disabilita i Gemfile multisource in modo che i gem vengano solo dal repository previsto.

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

Imporre a livello di configurazione:

bundle config set disable_multisource true

CI/CD e Controlli del Registro che Aiutano

  • Registro privato come unico ingresso:
  • Utilizzare Artifactory/Nexus/CodeArtifact/GitHub Packages/Azure Artifacts come unico endpoint a cui gli sviluppatori/CI possono accedere.
  • Implementare regole di blocco/consenso in modo che i namespace interni non si risolvano mai da fonti pubbliche upstream.
  • I lockfile sono immutabili in CI:
  • npm: impegnare package-lock.json, utilizzare npm ci.
  • Yarn: impegnare yarn.lock, utilizzare yarn install --immutable.
  • Python: impegnare requirements.txt hashato, imporre --require-hashes.
  • Gradle: impegnare verification-metadata.xml e fallire su artefatti sconosciuti.
  • Controllo dell'uscita outbound: bloccare l'accesso diretto da CI a registri pubblici tranne che tramite il proxy approvato.
  • Riserva dei nomi: preregistrare i propri nomi/namespace interni nei registri pubblici dove supportato.
  • Provenienza dei pacchetti / attestazioni: quando si pubblicano pacchetti pubblici, abilitare la provenienza/attestazioni per rendere più rilevabile la manomissione a valle.

Riferimenti

tip

Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Impara e pratica il hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporta HackTricks