حمله Supply Chain در Dependency ها
Supply Chain Attack چیست و چگونه dependency های آلوده میتوانند کل زیرساخت شما را compromise کنند؟ در این مقاله با نمونههای واقعی حملات npm و PyPI، مفهوم Dependency Cooldown، روشهای جلوگیری از حملات زنجیره تأمین نرمافزار و بهترین راهکارهای امنیت 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 حذف شد. اما هر کسی که در همان چند ساعت اون رو نصب کرده بود، آلوده شده بود.
حمله LiteLLM
در PyPI نسخههایی از LiteLLM منتشر شد که credential های cloud، SSH key ها و Kubernetes config ها را harvest میکردند. پنجره حمله فقط حدود دو ساعت بود.
حمله Telnyx Python SDK
در این مورد backdoor فقط هنگام import فعال میشد؛ یعنی حتی بعضی از اسکنر ها هم متوجه آن نمیشدند و واقعا خطرناک بود.
حمله معروف xz-utils
این مورد کمی متفاوت بود. مهاجم ماهها به عنوان توسعه دهنده فعالیت کرد تا اعتماد بقیه رو جلب کنه و در نهایت Backdoor را وارد پروژه کرد. این حمله نشان داد که حتی پروژههای بسیار بالغ هم مصون نیستند.
الگوی مشترک حملهها
تقریباً همه این حملهها یک الگوی مشابه دارند:
- مهاجم پکیج را آلوده میکنه
- نسخه مخرب منتشر میشه
- کاربران سریع پکیج رو نصب/آپدیت میکنن
- چند ساعت بعد محققین امنیتی یا اسکنرها متوجه میشن
- پکیج آلوده حذف میشه
مشکل اینجاست که قربانیها معمولاً همان افرادی هستند که «زودتر از همه Update میکنند».
راهکارها برای کاهش ریسک
برای این نوع حمله هیچ راهحل جادویی وجود نداره، ولی میتونیم ریسک را خیلی کم کنیم.
۱. Version Pinning
به جای:
requests>=2.0از نسخه مشخص استفاده کنید مثلا:
requests==2.32.0و حتما باید lockfile داشته باشید:
package-lock.jsonpnpm-lock.yamluv.lockpoetry.lock
این کار جلوی تغییر ناگهانی Dependency Tree را میگیرد.
۲. استفاده از Private Registry / Mirror
خیلی از شرکتها Dependency ها را مستقیم از npm یا PyPI نمیگیرند.
بلکه از رجیستری هایی مانند موارد زیر استفاده میکنند:
- Artifactory
- Nexus
- Verdaccio
- Internal Mirror
در این صورت تمام Dependency ها قبل از ورود به چرخه استفاده، اسکن و تایید میشوند.
۳. اسکن پکیج ها
ابزارهایی مثل:
npm auditpip-auditDependabotRenovateTrivyOSV Scanner
میتوانند Vulnerability یا پکیجهای مشکوک را پیدا کنند.
اما یک مشکل مهم وجود دارد:
این ابزارها معمولاً بعد از انتشار پکیج مخرب متوجه حمله میشوند.
یعنی اگر شما همان لحظه پکیج را نصب کرده باشید، دیر شده است.

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 buildGolang
هنوز به طور رسمی پشتیبانی از Cooldown بهش اضافه نشده ولی یک پروپوزال باز داره و در حال توسعه است.
NuGet
Composer
Hex
Java - Maven / Gradle
هنوز پشتیبانی نمیشود ‼️
Ruby - RubyGems / Bundler
هنوز پشتیبانی نمیشود ‼️
ولی یه Package Index جانبی هست که به صورت اجباری Cooldown دو روزه برای تمام پکیج ها قرار داده.
Swift
هنوز پشتیبانی نمیشود ‼️

چرا 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 هایتان فعال نیست، احتمالاً الان بهترین زمان برای انجام آن است.