Bắt đầu nhanh: Quét dự án trong 5 phút
Chạy lệnh này ngay trên dự án hiện tại của bạn. Bạn cần pip-audit cho Python và npm audit cho Node — cả hai đều hoạt động trong hầu hết môi trường mà không cần cài thêm gì.
# Python: kiểm tra các package đã cài
pip install pip-audit
pip-audit
# Node.js: kiểm tra dependency npm
npm audit
npm audit --json | jq '.vulnerabilities | keys'
Nếu thấy bất kỳ cảnh báo nào được đánh dấu high hoặc critical, hãy dừng lại trước khi deploy. Đây là bước kiểm tra cơ bản — nhưng nó chỉ phát hiện các CVE đã biết. Dependency confusion và typosquatting là dạng tấn công hoàn toàn khác, và các công cụ audit thông thường sẽ bỏ sót chúng. Đó là khoảng trống mà hướng dẫn này sẽ lấp đầy.
Các cuộc tấn công này thực sự diễn ra như thế nào
Dependency Confusion (Nhầm lẫn namespace)
Cơ chế rất đơn giản nhưng cực kỳ nguy hiểm. Công ty bạn host một package nội bộ — ví dụ acme-utils — trên một registry npm riêng hoặc mirror PyPI. Kẻ tấn công đăng một package cùng tên trên registry công khai, nhưng đặt số phiên bản cao hơn. Khi CI/CD pipeline chạy npm install hoặc pip install, package manager sẽ tải về package công khai đó. Phiên bản cao hơn thắng. Đơn giản vậy thôi.
Alex Birsan đã chứng minh điều này vào năm 2021 và nhận bug bounty từ Microsoft, Apple, Shopify cùng 32 công ty khác chỉ trong một lần công bố nghiên cứu. Payload thực thi hoàn toàn tự động — không cần tương tác người dùng, không cần quyền đặc biệt. Chỉ cần npm install.
Typosquatting
Kẻ tấn công đăng ký requets thay vì requests, hoặc cros-env thay vì cross-env. Rồi chúng chờ đợi. Lập trình viên gõ nhầm. Script CI được copy-paste cùng với lỗi đánh máy. Áp lực deadline khiến người ta bất cẩn — và chỉ cần một ký tự sai trong tên dependency là đủ để xảy ra thảm họa.
colourama (typo của colorama) đã được tải xuống hơn 500 lần trước khi PyPI gỡ xuống. Payload độc hại chạy ngay lúc cài đặt thông qua script postinstall trong npm hoặc setup.py trong pip — trước khi bạn kịp import package vào code.
Đi sâu vào kỹ thuật phát hiện
Phát hiện Typosquatting với difflib và script tùy chỉnh
Đối chiếu requirements.txt của bạn với danh sách package hợp lệ đã biết bằng fuzzy matching. Bất kỳ tên nào có độ tương đồng trên 0.85 so với tên package thật — nhưng không có trong whitelist — sẽ bị gắn cờ cảnh báo.
import difflib
# Danh sách package hợp lệ đã biết (cần cập nhật thường xuyên)
KNOWN_PACKAGES = [
"requests", "flask", "django", "numpy", "pandas",
"boto3", "fastapi", "pydantic", "sqlalchemy", "celery"
]
def check_typosquatting(package_name, threshold=0.85):
matches = difflib.get_close_matches(
package_name,
KNOWN_PACKAGES,
n=3,
cutoff=threshold
)
if matches and package_name not in KNOWN_PACKAGES:
print(f"[CẢNH BÁO] '{package_name}' trông giống với: {matches}")
return True
return False
# Đọc từ requirements.txt
with open("requirements.txt") as f:
for line in f:
pkg = line.strip().split("==")[0].split(">")[0].split("<")[0]
if pkg:
check_typosquatting(pkg)
Không hoàn hảo — nhưng đây là bước lọc đầu tiên với chi phí thấp, bắt được những trường hợp rõ ràng nhất. Tích hợp vào bất kỳ CI pipeline nào và nó sẽ chứng minh giá trị ngay lần đầu tiên phát hiện vấn đề.
Dùng pip-audit và Safety để phát hiện package độc hại đã biết
# pip-audit kiểm tra dựa trên PyPI Advisory Database
pip-audit -r requirements.txt --fix
# Safety kiểm tra dựa trên cơ sở dữ liệu lỗ hổng được quản lý thủ công
pip install safety
safety check -r requirements.txt --full-report
npm: Phát hiện script postinstall đáng ngờ
Hầu hết package npm độc hại đều dùng lifecycle script để thực thi code lúc cài đặt. Hãy kiểm tra xem package khai báo gì trước khi chạy nó:
# Kiểm tra lifecycle script trước khi cài đặt
npm pack some-package --dry-run
npx can-i-ignore-scripts some-package
# Cài đặt với script bị tắt (có thể làm hỏng một số package hợp lệ, nhưng an toàn khi audit)
npm install --ignore-scripts
# Quét các package đã cài để tìm postinstall hook
cat node_modules/*/package.json | jq -r 'select(.scripts.postinstall) | "\(.name): \(.scripts.postinstall)"'
Kiểm tra metadata package để tìm dấu hiệu đáng ngờ
# npm: kiểm tra ngày publish, số maintainer, thống kê download
npx npm-package-stats some-package
# PyPI: lấy metadata qua API
curl -s https://pypi.org/pypi/requests/json | jq '{
author: .info.author,
maintainers: .info.maintainer,
home_page: .info.home_page,
upload_time: .urls[0].upload_time
}'
Mới đăng hôm qua. Không có link GitHub. Không có thông tin maintainer. Tên trùng với tooling nội bộ của bạn. Đây chính xác là dấu hiệu nhận dạng của một dependency confusion payload — bốn cờ đỏ cùng lúc.
Nâng cao: Khóa chặt chuỗi cung ứng
Scoped package và cấu hình registry (npm)
Đặt các package nội bộ dưới tên org của bạn, rồi cấu hình npm để resolve scope đó chỉ từ registry riêng:
# .npmrc đặt ở thư mục gốc dự án
@mycompany:registry=https://npm.mycompany.internal
always-auth=true
Với cấu hình này, npm sẽ không bao giờ truy cập registry công khai cho các package @mycompany/*. Dependency confusion bị vô hiệu hóa ở tầng cấu hình — không cần thay đổi code.
Ưu tiên index trong pip
Dùng --index-url để đặt registry riêng làm nguồn chính, với --extra-index-url làm fallback cho package công khai:
# pip.conf — registry riêng làm nguồn chính
[global]
index-url = https://pypi.mycompany.internal/simple/
extra-index-url = https://pypi.org/simple/
Cách an toàn hơn là bỏ hoàn toàn extra-index-url cho các package nội bộ. Vendor trực tiếp, hoặc dùng registry proxy như Nexus hoặc Artifactory để mirror PyPI và cho phép bạn áp dụng allow-list. Cách này loại bỏ sự mơ hồ khi pip gặp cùng một tên package tồn tại ở cả hai registry.
Lockfile là bắt buộc, không có ngoại lệ
Hãy commit lockfile của bạn. Luôn luôn. Không có ngoại lệ.
# Node.js — commit package-lock.json hoặc yarn.lock
git add package-lock.json
# Python — tạo và commit file requirements đã được pin phiên bản
pip freeze > requirements.lock
git add requirements.lock
# CI cài đặt từ lockfile, không phải requirements.txt
pip install -r requirements.lock
npm ci # không dùng npm install
npm ci là điểm khác biệt quan trọng ở đây. Nó cài đặt chính xác những gì có trong package-lock.json và báo lỗi ngay khi có bất kỳ sự không khớp nào, thay vì âm thầm resolve sang phiên bản mới hơn.
Xác minh tính toàn vẹn package bằng hash
# pip: bắt buộc xác minh hash
pip install --require-hashes -r requirements.txt
# Tạo requirements có hash bằng pip-compile (từ pip-tools)
pip install pip-tools
pip-compile --generate-hashes requirements.in
Một file requirements.txt được pin hash trông như thế này:
requests==2.31.0 \
--hash=sha256:58cd2187423d77b8d5e82b788 \
--hash=sha256:942c5a758f98d790eaed1a29c
Package tải về không khớp với hash đã liệt kê? pip sẽ từ chối cài đặt. Điều này bảo vệ bạn khỏi cả package bị giả mạo lẫn tấn công MITM vào chính registry.
Mẹo thực tế từ các môi trường triển khai thực tế
Quét tự động trong CI/CD
Tích hợp các bước này vào pipeline của GitHub Actions hoặc GitLab CI — chúng chạy dưới 30 giây và bắt vấn đề trước khi lên production:
# .github/workflows/security.yml
jobs:
dependency-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Kiểm tra Python
run: |
pip install pip-audit
pip-audit -r requirements.txt
- name: Kiểm tra npm
run: npm audit --audit-level=high
- name: Kiểm tra script cài đặt đáng ngờ
run: |
cat node_modules/*/package.json 2>/dev/null | \
python3 -c "
import sys, json
for line in sys.stdin:
try:
pkg = json.loads(line)
scripts = pkg.get('scripts', {})
if any(k in scripts for k in ['preinstall','install','postinstall']):
print(f\"{pkg.get('name')}: {scripts}\")
except: pass
"
Đặt trước tên namespace
Nếu các package nội bộ của bạn chưa được scoped, hãy tự đăng ký tên đó trên registry công khai. Đăng các package placeholder rỗng kèm README cảnh báo rõ ràng. Tốn vài phút làm một lần và ngăn chặn việc chiếm đoạt tên mãi mãi.
# Đăng ký placeholder trên PyPI
# Tạo một setup.py tối giản, thêm cảnh báo rõ ràng trong README
# rằng đây là package nội bộ và phiên bản public chỉ là placeholder
python setup.py sdist upload
Review dependency trong Pull Request
GitHub’s Dependency Review Action chặn các PR đưa vào dependency dễ bị tấn công hoặc dependency mới có rủi ro cao — trước khi code được merge:
- uses: actions/dependency-review-action@v4
with:
fail-on-severity: high
deny-licenses: GPL-2.0, AGPL-3.0
Giữ thông tin xác thực registry thật mạnh
Registry riêng cần xác thực, và các thông tin đăng nhập đó là mục tiêu tấn công. Hãy xoay vòng chúng thường xuyên và tạo đúng cách — 32+ ký tự, đầy đủ các loại ký tự. Tôi dùng toolcraft.app/vi/tools/security/password-generator riêng cho registry token: nó chạy hoàn toàn trên trình duyệt, không có gì gửi qua mạng.
Giám sát các đăng ký package mới trùng tên nội bộ
Các dịch vụ như socket.dev theo dõi npm theo thời gian thực và cảnh báo khi có package mới xuất hiện khớp với pattern bạn định nghĩa. Thiết lập cảnh báo cho tên các package nội bộ của bạn — bạn muốn biết trong vòng vài phút, không phải vài ngày.
# Tích hợp socket.dev CLI
npm install -g @socketsecurity/cli
socket scan .
Khóa lockfile. Scope các package nội bộ. Tắt script khi có thể. Quét trong CI. Bốn thói quen giúp loại bỏ phần lớn bề mặt tấn công — và không cái nào đòi hỏi một đội ngũ bảo mật chuyên trách để thực hiện.

