Confusión de Dependencias
Reading time: 10 minutes
tip
Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Hacking en Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.
Información Básica
La Confusión de Dependencias (también conocida como ataques de sustitución) ocurre cuando un gestor de paquetes resuelve un nombre de dependencia de un registro/fuente no intencionado y menos confiable (generalmente un registro público) en lugar del privado/interno previsto. Esto generalmente conduce a la instalación de un paquete controlado por un atacante.
Causas raíz comunes:
- Typosquatting/mal escritura: Importar
reqests
en lugar derequests
(se resuelve desde el registro público). - Paquete interno inexistente/abandonado: Importar
company-logging
que ya no existe internamente, por lo que el resolvedor busca en registros públicos y encuentra un paquete del atacante. - Preferencia de versión a través de múltiples registros: Importar un
company-requests
interno mientras se permite que el resolvedor también consulte registros públicos y prefiera la versión “mejor”/más nueva publicada públicamente por un atacante.
Idea clave: Si el resolvedor puede ver múltiples registros para el mismo nombre de paquete y se le permite elegir el candidato “mejor” globalmente, eres vulnerable a menos que restrinjas la resolución.
Explotación
warning
En todos los casos, el atacante solo necesita publicar un paquete malicioso con el mismo nombre que la dependencia que tu construcción resuelve desde un registro público. Los hooks en el momento de la instalación (por ejemplo, scripts de npm) o los caminos de código en el momento de la importación a menudo permiten la ejecución de código.
Mal escrito e Inexistente
Si tu proyecto hace referencia a una biblioteca que no está disponible en el registro privado, y tus herramientas retroceden a un registro público, un atacante puede sembrar un paquete malicioso con ese nombre en el registro público. Tus runners/máquinas de CI/dev lo buscarán y lo ejecutarán.
Versión no especificada / selección de “mejor versión” a través de índices
Los desarrolladores frecuentemente dejan versiones sin fijar o permiten rangos amplios. Cuando un resolvedor está configurado con índices internos y públicos, puede seleccionar la versión más nueva sin importar la fuente. Para nombres internos como requests-company
, si el índice interno tiene 1.0.1
pero un atacante publica 1.0.2
en el registro público y tu resolvedor considera ambos, el paquete público puede ganar.
Solución de AWS
Esta vulnerabilidad fue encontrada en AWS CodeArtifact (lee los detalles en esta publicación de blog). AWS agregó controles para marcar dependencias/feeds como internas o externas para que el cliente no obtenga nombres “internos” de registros públicos ascendentes.
Encontrando Bibliotecas Vulnerables
En la publicación original sobre la confusión de dependencias, el autor buscó miles de manifiestos expuestos (por ejemplo, package.json
, requirements.txt
, archivos de bloqueo) para inferir nombres de paquetes internos y luego publicó paquetes de versiones superiores en registros públicos.
Manual Práctico del Atacante (para equipos rojos en pruebas autorizadas)
- Enumerar nombres:
- Grep repos y configuraciones de CI para archivos de manifiesto/bloqueo y espacios de nombres internos.
- Buscar prefijos específicos de la organización (por ejemplo,
@company/*
,company-*
, groupIds internos, patrones de ID de NuGet, rutas de módulos privados para Go, etc.). - Verificar la disponibilidad en registros públicos:
- Si el nombre no está registrado públicamente, regístralo; si existe, intenta el secuestro de subdependencias apuntando a nombres transitivos internos.
- Publicar con precedencia:
- Elegir un semver que “gane” (por ejemplo, una versión muy alta) o que coincida con las reglas del resolvedor.
- Incluir ejecución mínima en el momento de la instalación donde sea aplicable (por ejemplo, scripts de npm
preinstall
/install
/postinstall
). Para Python, preferir caminos de ejecución en el momento de la importación, ya que los wheels típicamente no ejecutan código arbitrario en la instalación. - Exfiltrar control:
- Asegurarse de que se permita la salida desde CI a tu punto final controlado; de lo contrario, usar consultas DNS o mensajes de error como un canal lateral para probar la ejecución de código.
caution
Siempre obtén autorización por escrito, usa nombres/versiones de paquetes únicos para el compromiso y despublica inmediatamente o coordina la limpieza cuando concluyan las pruebas.
Manual del Defensor (lo que realmente previene la confusión)
Estrategias de alto nivel que funcionan en todos los ecosistemas:
- Usar espacios de nombres internos únicos y vincularlos a un solo registro.
- Evitar mezclar niveles de confianza en el momento de la resolución. Preferir un solo registro interno que actúe como proxy para paquetes públicos aprobados en lugar de dar a los gestores de paquetes tanto puntos finales internos como públicos.
- Para gestores que lo soporten, mapear paquetes a fuentes específicas (sin “mejor versión” global a través de registros).
- Fijar y bloquear:
- Usar archivos de bloqueo que registren las URL de registro resueltas (npm/yarn/pnpm) o usar fijación de hash/atestación (pip
--require-hashes
, verificación de dependencias de Gradle). - Bloquear la retroalimentación pública para nombres internos en la capa de registro/red.
- Reservar tus nombres internos en registros públicos cuando sea posible para prevenir futuros squats.
Notas del Ecosistema y Fragmentos de Configuración Segura
A continuación se presentan configuraciones pragmáticas y mínimas para reducir o eliminar la confusión de dependencias. Preferir hacer cumplir estas en entornos de CI y desarrolladores.
JavaScript/TypeScript (npm, Yarn, pnpm)
- Usar paquetes con alcance para todo el código interno y fijar el alcance a tu registro privado.
- Mantener instalaciones inmutables en CI (archivo de bloqueo de npm,
yarn install --immutable
).
.npmrc (nivel de proyecto)
# 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 (para paquete 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
Consejos operativos:
- Solo publica paquetes internos dentro del alcance
@company
. - Para paquetes de terceros, permite el registro público a través de tu proxy/espejo privado, no directamente desde los clientes.
- Considera habilitar la procedencia de paquetes npm para los paquetes públicos que publiques para aumentar la trazabilidad (no previene la confusión por sí mismo).
Python (pip / Poetry)
Regla principal: No uses --extra-index-url
para mezclar niveles de confianza. O bien:
- Expón un único índice interno que proxy y almacene en caché los paquetes aprobados de PyPI, o
- Usa selección de índice explícita y fijación de 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
Generar requisitos hash con pip-tools:
# From pyproject.toml or requirements.in
pip-compile --generate-hashes -o requirements.txt
pip install --require-hashes -r requirements.txt
Si debes acceder a PyPI público, hazlo a través de tu proxy interno y mantén una lista de permitidos explícita allí. Evita --extra-index-url
en CI.
.NET (NuGet)
Utiliza el Mapeo de Fuentes de Paquetes para vincular patrones de ID de paquetes a fuentes explícitas y prevenir la resolución desde feeds inesperados.
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 (reflejar todo a interno; deshabilitar repos ad-hoc en POMs a través de Enforcer):
<settings>
<mirrors>
<mirror>
<id>internal-mirror</id>
<mirrorOf>*</mirrorOf>
<url>https://maven.corp.example/repository/group</url>
</mirror>
</mirrors>
</settings>
Agrega Enforcer para prohibir repositorios declarados en POMs y forzar el uso de tu espejo:
<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: Centralizar y bloquear dependencias.
- Hacer cumplir los repositorios en
settings.gradle(.kts)
solamente:
dependencyResolutionManagement {
repositoriesMode = RepositoriesMode.FAIL_ON_PROJECT_REPOS
repositories {
maven { url = uri("https://maven.corp.example/repository/group") }
}
}
- Habilitar la verificación de dependencias (sumas de verificación/firmas) y confirmar
gradle/verification-metadata.xml
.
Módulos Go
Configurar módulos privados para que no se utilicen el proxy público y la base de datos de sumas de verificación para ellos.
# 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)
Reemplace crates.io con un espejo interno aprobado o un directorio de proveedores para las compilaciones; no permita una recuperación pública arbitraria.
.cargo/config.toml
[source.crates-io]
replace-with = "corp-mirror"
[source.corp-mirror]
registry = "https://crates-mirror.corp.example/index"
Para la publicación, sé explícito con --registry
y mantén las credenciales limitadas al registro objetivo.
Ruby (Bundler)
Utiliza bloques de origen y desactiva los Gemfiles de múltiples fuentes para que los gems provengan solo del repositorio 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
Hacer cumplir a nivel de configuración:
bundle config set disable_multisource true
CI/CD y controles de registro que ayudan
- Registro privado como único ingreso:
- Utilizar Artifactory/Nexus/CodeArtifact/GitHub Packages/Azure Artifacts como el único punto de acceso que los desarrolladores/CI pueden alcanzar.
- Implementar reglas de bloqueo/permisos para que los espacios de nombres internos nunca se resuelvan desde fuentes públicas ascendentes.
- Los lockfiles son inmutables en CI:
- npm: confirmar
package-lock.json
, usarnpm ci
. - Yarn: confirmar
yarn.lock
, usaryarn install --immutable
. - Python: confirmar
requirements.txt
con hash, hacer cumplir--require-hashes
. - Gradle: confirmar
verification-metadata.xml
y fallar en artefactos desconocidos. - Control de salida: bloquear el acceso directo desde CI a registros públicos excepto a través del proxy aprobado.
- Reserva de nombres: pre-registrar sus nombres/espacios de nombres internos en registros públicos donde sea compatible.
- Procedencia de paquetes / atestaciones: al publicar paquetes públicos, habilitar procedencia/atestaciones para hacer que la manipulación sea más detectable a nivel inferior.
Referencias
- https://medium.com/@alex.birsan/dependency-confusion-4a5d60fec610
- https://zego.engineering/dependency-confusion-in-aws-codeartifact-86b9ff68963d
- https://learn.microsoft.com/en-us/nuget/consume-packages/package-source-mapping
- https://yarnpkg.com/configuration/yarnrc/
tip
Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Hacking en Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.