حمله Supply Chain در Dependency ها

Supply Chain Attack چیست و چگونه dependency های آلوده می‌توانند کل زیرساخت شما را compromise کنند؟ در این مقاله با نمونه‌های واقعی حملات npm و PyPI، مفهوم Dependency Cooldown، روش‌های جلوگیری از حملات زنجیره تأمین نرم‌افزار و بهترین راهکارهای امنیت dependency آشنا می‌شوید.

حمله Supply Chain در Dependency ها

چند سال پیش اگر کسی می‌گفت بزرگ‌ترین ریسک امنیتی پروژه‌ات ممکنه کدی باشد که خودت ننوشتی، شاید عجیب به نظر می‌رسید.
اما امروز بخش بزرگی از نرم‌افزارها روی صدها یا حتی هزاران dependency ساخته می‌شوند؛ dependency هایی که خیلی وقت‌ها حتی اسمشان را هم نمی‌دانیم. کافی است یکی از این پکیج‌ها compromise بشه تا کل زنجیره آلوده بشه و مشکلات امنیتی شدیدی پیدا کنیم.

این دقیقاً همون چیزیه که بهش Supply Chain Attack می‌گیم.

Supply Chain Attack چیست؟

در این مدل حمله، مهاجم به جای حمله مستقیم به اپلیکیشن شما، سراغ یکی از اجزای زنجیره توسعه نرم‌افزار می‌رود:

  • Dependency ها
  • Package Registry ها
  • CI/CD
  • Build Tools
  • Plugins
  • SDK ها
  • حتی Maintainer های پروژه‌های متن‌باز

هدف اصلی اینه که کد مخرب از یک مسیر معتبر وارد سیستم بشه.

مثلاً:

npm install something

یا:

pip install something

و تمام.

از اون لحظه به بعد، کد مهاجم روی لپ‌تاپ توسعه دهنده، رانرهای CI/CD، ایمیج داکر یا کلاستر Kubernetes شما اجرا میشه.

چرا این حمله‌ها این‌قدر خطرناک هستند؟

خیلی ساده است. چون Dependency/Package ها از نظر اکثر ما Trusted ( قابل اطمینان ) هستند و فرض رو بر این می‌گیریم که هیچ مشکلی ندارند. بالاخره یه سری ها بودن که روی توسعه و انتشارش نظارت داشته باشن !!

وقتی شما یک پکیج رو نصب می‌کنید، بهش اجازه می‌دین:

  • اسکریپت اجرا کنه
  • دسترسی به شبکه داشته باشه
  • فایل های مختلف رو در محیطی که اجرا شده بخونه
  • Environment Variable ها را ببیند
  • Credential ها را بخواند
  • داخل محیط CI/CD اجرا بشه

در واقع خیلی وقت‌ها Dependency ها از خود Application هم دسترسی های بیشتری می‌تونن داشته باشن.

چند نمونه واقعی از حملات اخیر

حمله به axios

یکی از معروف‌ترین اتفاقات اخیر مربوط به پکیج axios بود؛ پکیجی که میلیون‌ها دانلود هفتگی دارد. نسخه مخرب منتشر شد و در بازه کوتاهی توسط npm حذف شد. اما هر کسی که در همان چند ساعت اون رو نصب کرده بود، آلوده شده بود.

Post Mortem: axios npm supply chain compromise · Issue #10636 · axios/axios
Post Mortem: axios npm supply chain compromise Date: March 31, 2026 Author: Jason Saayman Status: Remediation in progress On March 31, 2026, two malicious versions of axios (1.14.1 and 0.30.4) were…

حمله LiteLLM

در PyPI نسخه‌هایی از LiteLLM منتشر شد که credential های cloud، SSH key ها و Kubernetes config ها را harvest می‌کردند. پنجره حمله فقط حدود دو ساعت بود.

حمله Telnyx Python SDK

در این مورد backdoor فقط هنگام import فعال می‌شد؛ یعنی حتی بعضی از اسکنر ها هم متوجه آن نمی‌شدند و واقعا خطرناک بود.

حمله معروف xz-utils

این مورد کمی متفاوت بود. مهاجم ماه‌ها به عنوان توسعه دهنده فعالیت کرد تا اعتماد بقیه رو جلب کنه و در نهایت Backdoor را وارد پروژه کرد. این حمله نشان داد که حتی پروژه‌های بسیار بالغ هم مصون نیستند.

الگوی مشترک حمله‌ها

تقریباً همه این حمله‌ها یک الگوی مشابه دارند:

  1. مهاجم پکیج را آلوده می‌کنه
  2. نسخه مخرب منتشر می‌شه
  3. کاربران سریع پکیج رو نصب/آپدیت می‌کنن
  4. چند ساعت بعد محققین امنیتی یا اسکنرها متوجه می‌شن
  5. پکیج آلوده حذف می‌شه

مشکل اینجاست که قربانی‌ها معمولاً همان افرادی هستند که «زودتر از همه Update می‌کنند».

راهکارها برای کاهش ریسک

برای این نوع حمله هیچ راه‌حل جادویی وجود نداره، ولی می‌تونیم ریسک را خیلی کم کنیم.

۱. Version Pinning

به جای:

requests>=2.0

از نسخه مشخص استفاده کنید مثلا:

requests==2.32.0

و حتما باید lockfile داشته باشید:

  • package-lock.json
  • pnpm-lock.yaml
  • uv.lock
  • poetry.lock

این کار جلوی تغییر ناگهانی Dependency Tree را می‌گیرد.


۲. استفاده از Private Registry / Mirror

خیلی از شرکت‌ها Dependency ها را مستقیم از npm یا PyPI نمی‌گیرند.

بلکه از رجیستری هایی مانند موارد زیر استفاده می‌کنند:

  • Artifactory
  • Nexus
  • Verdaccio
  • Internal Mirror

در این صورت تمام Dependency ها قبل از ورود به چرخه استفاده، اسکن و تایید می‌شوند.


۳. اسکن پکیج ها

ابزارهایی مثل:

  • npm audit
  • pip-audit
  • Dependabot
  • Renovate
  • Trivy
  • OSV Scanner

می‌توانند Vulnerability یا پکیج‌های مشکوک را پیدا کنند.

اما یک مشکل مهم وجود دارد:

این ابزارها معمولاً بعد از انتشار پکیج مخرب متوجه حمله می‌شوند.

یعنی اگر شما همان لحظه پکیج را نصب کرده باشید، دیر شده است.


Generated by ChatGPT

Dependency Cooldown؛ راهکاری ساده ولی بسیار مؤثر

ایده Dependency Cooldown خیلی ساده است:

هیچ پکیج جدیدی را تا چند روز بعد از انتشار نصب نکن.

مثلاً:

  • اگر نسخه جدید پکیج امروز منتشر شد -> سیستم شما تا ۳ روز اجازه نصب آن را ندهد

در این مدت:

  • محقق ها پکیج را بررسی می‌کنند
  • اسکنر ها آن را تحلیل می‌کنند
  • اگر مشکوک/آلوده باشد سریع گزارش می‌شود

طبق بررسی‌هایی که روی چندین حمله واقعی انجام شده، بیشتر حملات Supply Chain کمتر از یک هفته فعال بوده‌اند و بسیاری فقط چند ساعت دوام آورده‌اند. یعنی فقط با چند روز تاخیر می‌توان درصد بزرگی از این حملات را خنثی کرد.

انتخاب این روش بسیار ساده است و توسط خود Package Manager ها انجام می‌شود. فقط باید از آن استفاده کنید.

جاوا اسکریپت - npm

در نسخه‌های جدید npm می‌توانید از آپشن min-release-age استفاده کنید. این آپشن از نسخه 11.10.0 به بعد در دسترس است:

npm config set min-release-age=3

یعنی پکیج باید حداقل ۳ روز از انتشارش گذشته باشد تا نصب شود. همچنین می‌تواند در فایل .npmrc نیز این مورد را تنظیم کنید:

min-release-age = 3

مستندات رسمی

جاوا اسکریپت - pnpm

در نسخه 10.16.0 آپشن minimumReleaseAge اضافه شده است که می‌تونید توی فایل ~/.config/pnpm/rc یا کانفیگ خود پروژه ازش استفاده کنید.

minimumReleaseAge: 4320

که بر حسب دقیقه است (۳ روز). همچنین قابلیت Exclude کردن بعضی پکیج ها نیز وجود دارد (در npm هنوز چنین قابلیتی وجود ندارد):

minimumReleaseAge: 4320
minimumReleaseAgeExclude:
- webpack
- react

مستندات رسمی

جاوا اسکریپت - Yarn

در نسخه 4.10.0 آپشن npmMinimalAgeGate اضافه شده است که می‌تونید توی فایل .yarnrc.yml تنظیم کنید:

npmMinimalAgeGate: "3d"

همچنین قابلیت Exclude کردن بعضی پکیج ها نیز وجود دارد:

npmMinimalAgeGate: "3d"
npmPreapprovedPackages:
  - typescript
  - eslint

مستندات رسمی

جاوا اسکریپت - Bun

در نسخه 1.3.0 آپشن minimumReleaseAge اضافه شده است که می‌تونید توی فایل bunfig.toml تنظیم کنید:

[install]
minimumReleaseAge = 259200 # 3 days

در اینجا واحد زمان بر حسب ثانیه است.

مستندات رسمی

جاوا اسکریپت - Deno

در نسخه 2.6 آپشن minimumDependencyAge اضافه شده است که می‌تونید توی فایل deno.json تنظیم کنید:

{
  "minimumDependencyAge": "P3D"
}

یا از آرگومان --minimum-dependency-age در CLI استفاده کنید:

deno install --minimum-dependency-age=P3D
deno update --minimum-dependency-age=P3D
deno outdated --minimum-dependency-age=P3D

در اینجا واحد زمان می‌تواند بر حسب دقیق ، استاندارد ISO8601 ( مانند "P3D" برای ۳ روز ) یا RFC3339 تنظیم شود.

مستندات رسمی

پایتون - uv

در نسخه 0.9.17 قابلیت Cooldown معرفی شد که با روش های مختلف و واحدهای زمانی متفاوت قابل استفاده است.

uv pip install --exclude-newer '3 days'

برای این که شامل تمام پروژه ها شود، می‌توانید به صورت سراسری در سیستم آن را در فایل ~/.config/uv/uv.toml تنظیم کنید:

exclude-newer = "3 days"

یا از ENV مربوطه استفاده کنید:

export UV_EXCLUDE_NEWER="3 days"

همچنین در سطح پروژه نیز قابل تعریف است:

[tool.uv]
exclude-newer = "3 days"

مستندات اصلی

پایتون - pip

در نسخه 26.1 پشتیبانی از Cooldown با استاندارد ISO8601 اضافه شد که با استفاده از آرگومان --uploaded-prior-to تنظیم میشه:

pip install --uploaded-prior-to P3D foo

همچنین ENV نیز در اختیار شما قرار گرفته است:

export PIP_UPLOADED_PRIOR_TO="P3D"

یا به صورت سراسری در فایل ~/.config/pip/pip.conf قابل تنظیم است:

[install]
uploaded-prior-to = P3D

در نسخه های قدیمی pip ، شما باید از Absolute Timestamp استفاده می‌کردید که دردسرهای خودش رو داشت و دیگه بهش نمی‌پردازیم ( کسی از نسخه های قدیمی استفاده نمی‌کنه 😃 ).

مستندات اصلی

پایتون - Conda

هنوز پشتیبانی Cooldown به Conda اضافه نشده ولی یک Issue باز داره و در حال توسعه است.

Rust - Cargo

تا الان به صورت رسمی پشتیبانی از Cooldown به Cargo اضافه نشده و مثل Conda یک Issue باز داره. ولی تا اونموقع می‌تونید از ابزار cargo-cooldown استفاده کنید.

cargo install cargo-cooldown
export COOLDOWN_MINUTES=4320  # 3 days, in minutes
cargo cooldown build

Golang

هنوز به طور رسمی پشتیبانی از Cooldown بهش اضافه نشده ولی یک پروپوزال باز داره و در حال توسعه است.

NuGet

در حال توسعه است.

Composer

در حال توسعه است.

Hex

در حال توسعه است.

Java - Maven / Gradle

هنوز پشتیبانی نمی‌شود ‼️

Ruby - RubyGems / Bundler

هنوز پشتیبانی نمی‌شود ‼️

ولی یه Package Index جانبی هست که به صورت اجباری Cooldown دو روزه برای تمام پکیج ها قرار داده.

https://gem.coop

Swift

هنوز پشتیبانی نمی‌شود ‼️

Generated by ChatGPT

چرا Dependency Cooldown واقعاً جواب می‌دهد؟

چون شما قرار نیست نقش محقق و شناسایی کننده آلودگی را بازی کنید.

البته خیلی‌ها اشتباه فکر می‌کنند اگر همه Cooldown فعال کنند، دیگر کسی پکیج مخرب را کشف نمی‌کنه. در عمل کشف این حمله‌ها معمولاً توسط این‌ها انجام می‌شود:

  • Automated Security Scanners
  • Threat Researchers
  • Registry Monitoring Systems
  • Honeypot Infrastructure

نه توسط توسعه دهنده های عادی.

توجه داشته باشید Cooldown فقط کاری می‌کنه که شما جزو اولین قربانی‌ها نباشید.

محدودیت‌های Dependency Cooldown

در نظر داشته باشیم که Cooldown همه‌چیز رو واسه ما حل نمی‌کنه.

مثلاً جلوی این موارد را نمی‌گیرد:

  • Typosquatting
  • Dependency Confusion
  • Long-Term Maintainer Compromise
  • حمله‌هایی مثل xz-utils
  • انواع Vulnerability های قدیمی داخل پکیج های فعلی

برای همین باید در کنارش از این موارد هم استفاده کنیم:

  • استفاده از lockfile ها
  • hash pinning
  • SBOM
  • اسکن همیشگی پکیج ها قبل از انتشار نسخه پروداکشن
  • Provenance Verification
  • استفاده از Base Image های مینیمال
  • اجرای CI/CD در محیط ایزوله

جمع‌بندی

دنیای مدرن امروزی برای توسعه نرم افزارها عملاً روی Dependency/Package ها ساخته شده. مشکل اینجاست که هر پکیج برای ما یک Trust Boundary جدید ایجاد می‌کنه که باید بهش آگاه باشیم.

امروز ممکنه یک پکیج فقط برای چند ساعت آلوده باشه؛ ولی همان چند ساعت کافی است تا Credential ها، Token ها و حتی محیط پروداکشن شما Compromise شود.

Dependency Cooldown شاید ساده به نظر برسد، ولی دقیقاً به همین دلیل ارزشمند است:
  • هزینه تقریباً صفر
  • پیاده‌سازی ساده
  • کاهش شدید ریسک حملات سریع Supply Chain

اگر هنوز Cooldown برای Package Manager هایتان فعال نیست، احتمالاً الان بهترین زمان برای انجام آن است.