توی این درس یاد میگیری image ات چه آسیبپذیریهای شناختهشدهای دارد و چطور پیدایشان کنی. CVE چیست و شدت (LOW تا CRITICAL) یعنی چه، با Trivy یک image را اسکن میکنی (از داخل کانتینر و بدون دادن docker.sock)، image قدیمی را با جدید مقایسه میکنی، با --exit-code در CI جلوی انتشار image ناامن را میگیری، با trivy config خود Dockerfile را بررسی میکنی و با Docker Scout آشنا میشوی. تمرین اصلی: یک image قدیمی را اسکن و بهروزرسانی میکنی.
مسئله: کد دیگران در image توست
Section titled “مسئله: کد دیگران در image توست”image تو فقط کد خودت نیست: سیستمعامل پایه، کتابخانهها، و پکیجها همه کد دیگراناند. هر روز در این کدها آسیبپذیری تازه کشف میشود. اگر image دو سال پیش ساخته شده و دوباره ساخته نشده، احتمالاً حفرههای شناختهشده دارد که مهاجمان خودکار دنبالشان میگردند.
تشبیه: بازرسی ساختمان
Section titled “تشبیه: بازرسی ساختمان”اسکن آسیبپذیری مثل بازرس ایمنی ساختمان است: ساختمان (image) را با فهرست حفرههای شناختهشدهی ساختمانهای مشابه (پایگاه دادهی CVE) تطبیق میدهد و میگوید «این در قدیمی است، این سیمکشی». بازرس نمیگوید ساختمان امن است؛ فقط میگوید چه چیزهای شناختهشدهای هست. حفرههای ناشناخته را نمیبیند.
CVE و شدت
Section titled “CVE و شدت”CVE (Common Vulnerabilities and Exposures) یک شناسهی عمومی برای هر آسیبپذیری است: CVE-سال-شماره، مثل CVE-2022-37434. هر CVE یک شدت دارد (از امتیاز CVSS ساخته میشود):
| شدت | معنی | اقدام |
|---|---|---|
CRITICAL |
اجرای کد از راه دور یا خسارت بزرگ؛ سوءاستفاده ساده | فوراً رفع کن؛ در CI شکست بده |
HIGH |
خطر جدی، معمولاً به شرایط خاص وابسته | بهزودی رفع کن |
MEDIUM |
خطر متوسط | در برنامهی بهروزرسانی |
LOW |
خطر کم یا سخت قابلسوءاستفاده | پیگیری |
UNKNOWN |
هنوز امتیاز ندارد | بررسی دستی |
دو ستون مهم گزارش: Installed Version (نسخهی موجود در image) و Fixed Version (نسخهای که مشکل را رفع کرده؛ اگر خالی باشد، هنوز رفعی منتشر نشده و فقط باید جلو را بگیری).
مثالهای عملی
Section titled “مثالهای عملی”مثال ۱: اجرای Trivy با داکر (بدون نصب)
Section titled “مثال ۱: اجرای Trivy با داکر (بدون نصب)”Trivy یک ابزار متنباز است که مثل هر ابزار دیگری میتوان بهصورت یک image اجرایش کرد. بهجای دادن docker.sock (که در درس امنیت دیدی خطرناک است)، image را در یک فایل tar ذخیره میکنیم و Trivy فقط همان فایل را میبیند:
mkdir -p vs && cd vsdocker save -o p38.tar python:3.8-slimdocker save -o p312.tar python:3.12-slimls -l *.tar | awk '{printf "%s %.0f MB\n", $9, $5/1000000}'p312.tar 49 MBp38.tar 50 MBتابعی مینویسیم که Trivy را با دو volume اجرا کند: پوشهی فعلی (برای tar) و یک volume نامدار برای کش پایگاه داده تا هر بار دانلود نشود:
trivy() { docker run --rm -v "$PWD/vs":/w -v lx-trivy-cache:/root/.cache aquasec/trivy:latest "$@" --db-repository ghcr.io/aquasecurity/trivy-db:2; }trivy image --quiet --input /w/p312.tar --severity CRITICAL,HIGH --format table 2>&1 | grep -E 'Total|Report Summary|python' | head -4Report Summary│ usr/local/lib/python3.12/site-packages/pip-25.0.1.dist-info/METADATA │ python-pkg │ 0 │ - │Total: 45 (HIGH: 45, CRITICAL: 0)نکتهی --db-repository: Trivy پایگاه داده را از چند آدرس میگیرد؛ آدرس پیشفرض اول یک میرور گوگل است (mirror.gcr.io) که روی شبکهی من 403 میداد (در درس «محدودیت Docker Hub» همین الگو را دیدی). با این flag مستقیم از ghcr.io گرفتم و کار کرد. پایگاه داده بار اول چند ده مگابایت دانلود میشود و در volume میماند.
در گزارش Trivy اگر ستون Fixed Version مقدار دارد، یعنی چه؟
اگر خالی باشد هنوز رفعی نیست و باید ریسک را ارزیابی یا بسته را حذف کنی.
مثال ۲: image قدیمی در برابر جدید
Section titled “مثال ۲: image قدیمی در برابر جدید”برای شمردن بهجای خواندن صدها خط جدول، قالب خروجی را عوض میکنیم: فقط شدت هر آسیبپذیری را چاپ میکنیم و میشماریم:
trivy() { docker run --rm -v "$PWD/vs":/w -v lx-trivy-cache:/root/.cache aquasec/trivy:latest "$@" --db-repository ghcr.io/aquasecurity/trivy-db:2; }count() { trivy image --quiet --input /w/$1.tar --format template --template '{{ range . }}{{ range .Vulnerabilities }}{{ println .Severity }}{{ end }}{{ end }}' 2>/dev/null | sort | uniq -c | sort -rn | awk '{printf " %s: %s\n", $2, $1}'; }echo "python:3.8-slim (قدیمی):"; count p38echo "python:3.12-slim (جدید):"; count p312python:3.8-slim (قدیمی): MEDIUM: 216 LOW: 155 HIGH: 111 CRITICAL: 9 UNKNOWN: 5python:3.12-slim (جدید): MEDIUM: 65 LOW: 61 HIGH: 45 UNKNOWN: 2تفاوت بزرگ: image قدیمی چندین CRITICAL و صدها HIGH دارد؛ image جدید بدون CRITICAL و با تعداد خیلی کمتر. همان زبان پایتون، ولی base و کتابخانههای قدیمی. نتیجه: تازه نگه داشتن base مؤثرترین کار امنیتی ارزان است.
مثال ۳: یک آسیبپذیری را بخوان
Section titled “مثال ۳: یک آسیبپذیری را بخوان”trivy() { docker run --rm -v "$PWD/vs":/w -v lx-trivy-cache:/root/.cache aquasec/trivy:latest "$@" --db-repository ghcr.io/aquasecurity/trivy-db:2; }trivy image --quiet --input /w/p38.tar --severity CRITICAL --format template \ --template '{{ range . }}{{ range .Vulnerabilities }}{{ .PkgName }} | {{ .VulnerabilityID }} | نصبشده {{ .InstalledVersion }} | رفع در {{ if .FixedVersion }}{{ .FixedVersion }}{{ else }}هنوز نیست{{ end }}{{ println }}{{ end }}{{ end }}' 2>/dev/null | head -5libgnutls30 | CVE-2026-33845 | نصبشده 3.7.9-2+deb12u3 | رفع در 3.7.9-2+deb12u7libgnutls30 | CVE-2026-42010 | نصبشده 3.7.9-2+deb12u3 | رفع در 3.7.9-2+deb12u7libsqlite3-0 | CVE-2025-7458 | نصبشده 3.40.1-2 | رفع در هنوز نیستlibssl3 | CVE-2026-31789 | نصبشده 3.0.14-1~deb12u2 | رفع در 3.0.19-1~deb12u2openssl | CVE-2026-31789 | نصبشده 3.0.14-1~deb12u2 | رفع در 3.0.19-1~deb12u2هر ردیف: کدام بسته (PkgName)، کدام CVE، نسخهی نصبشده، و نسخهای که رفع کرده. برای بستهای که «رفع در» دارد، راهحل فقط بهروزرسانی است. برای «هنوز نیست»، باید ببینی مسیر سوءاستفاده در اپ تو واقعاً وجود دارد یا نه (و گاهی بسته را حذف کنی).
مثال ۴: رفع، بهروزرسانی بستهها یا base
Section titled “مثال ۴: رفع، بهروزرسانی بستهها یا base”مثال با یک image قدیمی Alpine: اسکن، رفع با apk upgrade در Dockerfile، و اسکن دوباره:
FROM alpine:3.12RUN apk upgrade --no-cachetrivy() { docker run --rm -v "$PWD/vs":/w -v lx-trivy-cache:/root/.cache aquasec/trivy:latest "$@" --db-repository ghcr.io/aquasecurity/trivy-db:2; }docker save -o vs/a312.tar alpine:3.12docker build -q -t lx-vs-fixed vs/fix >/dev/nulldocker save -o vs/fixed.tar lx-vs-fixedfor t in a312 fixed; do echo "== $t: $(trivy image --quiet --input /w/$t.tar --severity CRITICAL,HIGH --format template --template '{{ range . }}{{ range .Vulnerabilities }}{{ println .VulnerabilityID }}{{ end }}{{ end }}' 2>/dev/null | grep -c CVE) مورد HIGH/CRITICAL"done== a312: 1 مورد HIGH/CRITICAL== fixed: 0 مورد HIGH/CRITICALبعد از apk upgrade بستهی آسیبپذیر به نسخهی رفعشده رسید و گزارش پاک شد. دو راه رفع، به ترتیب اولویت: ۱) base جدیدتر (FROM python:3.12-slim)، که همهی بستهها را یکجا بهروز میکند؛ ۲) بهروزرسانی بستهها داخل Dockerfile (apk upgrade، apt-get upgrade)، که مفید است ولی نتیجه به زمان build وابسته میشود و تکرارپذیری را کم میکند. و بعد از رفع همیشه دوباره build و اسکن کن.
مثال ۵: شکستن CI با --exit-code
Section titled “مثال ۵: شکستن CI با --exit-code”اسکنر باید در CI جلوی انتشار image ناامن را بگیرد. با --exit-code 1 اگر آسیبپذیری با شدت انتخابشده پیدا شود، کد خروج غیرصفر میدهد و pipeline میشکند:
trivy() { docker run --rm -v "$PWD/vs":/w -v lx-trivy-cache:/root/.cache aquasec/trivy:latest "$@" --db-repository ghcr.io/aquasecurity/trivy-db:2; }for t in p38 p312; do trivy image --quiet --input /w/$t.tar --severity CRITICAL --exit-code 1 >/dev/null 2>&1 echo "$t: کد خروج $?"donep38: کد خروج 1p312: کد خروج 0image قدیمی با کد 1 رد میشود (CI میشکند) و image جدید با 0 عبور میکند. معمول: برای CRITICAL (و گاهی HIGH) بشکن، و بقیه را فقط گزارش کن. همچنین --ignore-unfixed آسیبپذیریهایی که هنوز رفع ندارند را نادیده میگیرد، چون کاری از دست تو برنمیآید:
trivy() { docker run --rm -v "$PWD/vs":/w -v lx-trivy-cache:/root/.cache aquasec/trivy:latest "$@" --db-repository ghcr.io/aquasecurity/trivy-db:2; }for f in "" "--ignore-unfixed"; do n=$(trivy image --quiet --input /w/p312.tar $f --severity HIGH,CRITICAL --format template --template '{{ range . }}{{ range .Vulnerabilities }}{{ println .VulnerabilityID }}{{ end }}{{ end }}' 2>/dev/null | grep -c CVE) echo "p312 ${f:-(همه)}: $n مورد HIGH/CRITICAL"donep312 (همه): 45 مورد HIGH/CRITICALp312 --ignore-unfixed: 1 مورد HIGH/CRITICALبا --ignore-unfixed عدد از ۴۵ به ۱ میرسد: بیشتر مواردِ python:3.12-slim در این روز هنوز رفعی در توزیع ندارند، پس کار عملی کمی از دست تو برمیآید و بقیه را فقط پایش میکنی. (این عددها با پایگاه دادهی امروز عوض میشوند.)
مثال ۶: اسکن خود Dockerfile
Section titled “مثال ۶: اسکن خود Dockerfile”trivy config بهجای image، فایلهای تنظیم (Dockerfile، Compose، Kubernetes) را برای اشتباهات امنیتی بررسی میکند:
FROM python:3.12-slimCOPY . .CMD ["python", "app.py"]docker run --rm -v "$PWD/vs":/w aquasec/trivy:latest config --quiet /w/cfg 2>&1 | grep -E '^DS-|Failures:'Failures: 2 (UNKNOWN: 0, LOW: 1, MEDIUM: 0, HIGH: 1, CRITICAL: 0)DS-0002 (HIGH): Specify at least 1 USER command in Dockerfile with non-root user as argumentDS-0026 (LOW): Add HEALTHCHECK instruction in your Dockerfiletrivy config پایگاه دادهی CVE لازم ندارد (قاعدهها داخل خود برنامهاند)، پس --db-repository نمیخواهد. دو اشتباه همانهایی هستند که در درسهای قبل گفتیم: بدون USER (اجرا با root) و بدون HEALTHCHECK. اسکن config در CI قبل از build، ارزانترین بازرسی است.
مثال ۷: Docker Scout (نمونه)
Section titled “مثال ۷: Docker Scout (نمونه)”Docker Scout ابزار رسمی داکر برای همین کار است و در Docker Desktop و CLI هست. نیاز به ورود (docker login) دارد و اطلاعات image (فهرست بستهها) را برای تحلیل به سرویس Docker میفرستد؛ دسترسی به آن از شبکهی تو ممکن است محدود باشد. من بدون ورود نمیتوانم اجرایش کنم، پس این بخش «نمونه» است:
docker logindocker scout quickview myapp:1.0 # خلاصه: تعداد CVE به تفکیک شدتdocker scout cves myapp:1.0 # فهرست کاملdocker scout cves --only-severity critical,high myapp:1.0docker scout recommendations myapp:1.0 # پیشنهاد base بهتر Target │ myapp:1.0 │ 1C 6H 9M 14L digest │ ...در عمل Trivy (محلی و بدون ارسال داده به بیرون) و Scout (یکپارچه با Docker Hub/Desktop) مکملاند؛ اگر شبکه یا سیاست سازمان اجازه نمیدهد، Trivy کافی است.
پشت پرده
Section titled “پشت پرده”Trivy image را اجرا نمیکند. لایهها را باز میکند و فهرست بستههای نصبشده را از روی پایگاههای دادهی بستهها (/var/lib/dpkg/status، /lib/apk/db/installed، فایلهای requirements.txt و package-lock.json و…) میخواند، سپس نسخهها را با پایگاه دادهی آسیبپذیری (ترکیبی از NVD و دادهی هر توزیع) تطبیق میدهد. پایگاه داده در یک OCI artifact است که از یک registry دانلود میشود (برای همین مسئلهی دسترسی به registry اینجا هم پیش آمد) و کش میشود.
چند نکتهی مهم برای تفسیر نتایج:
- مثبت کاذب: وجود یک بستهی آسیبپذیر یعنی خطر بالقوه؛ ممکن است اپ تو آن قسمت از کد را اصلاً استفاده نکند. ولی نباید همه را نادیده گرفت.
- اسکن یک عکس لحظهای است: image امروز تمیز است ولی هفتهی بعد CVE تازه کشف میشود. اسکن باید تکرار شود (CI و اسکن دورهای image های منتشرشده).
- Distroless و scratch (درس کوچک کردن image) بستهی کمتری دارند، پس آسیبپذیری کمتری هم گزارش میشود.
- اسکنر فقط آسیبپذیری شناختهشده را میگیرد؛ رمزهای لورفته (
trivy image --scanners secret) و پیکربندی بد (trivy config) را جدا میگیرد.
جدولهای مرجع
Section titled “جدولهای مرجع”| میخواهم… | دستور |
|---|---|
| اسکن image ذخیرهشده | trivy image --input x.tar |
| فقط شدتهای خاص | --severity CRITICAL,HIGH |
| نادیده گرفتن بیراهحلها | --ignore-unfixed |
| شکستن CI | --exit-code 1 |
| خروجی ماشینخوان | --format json / --format template |
| اسکن Dockerfile و Compose | trivy config DIR |
| اسکن رازهای داخل image | --scanners secret |
| آدرس پایگاه دادهی دیگر | --db-repository ghcr.io/aquasecurity/trivy-db:2 |
| تصمیم | معیار |
|---|---|
| CRITICAL با رفع موجود | فوراً بهروز کن |
| HIGH در بستهی استفادهشده | در همین هفته |
| بدون رفع (unfixed) | ارزیابی، mitigations، یا base دیگر |
| فقط در ابزار ساخت | multi-stage؛ از image نهایی حذف کن |
اشتباهات رایج
Section titled “اشتباهات رایج”۱) فکر کردن «اسکن تمیز = امن»
Section titled “۱) فکر کردن «اسکن تمیز = امن»”اسکنر فقط حفرههای شناختهشده را میبیند. راهحل: اسکن را با اصول درس امنیت (کاربر غیر root، حداقل دسترسی) ترکیب کن.
۲) اسکن فقط یک بار
Section titled “۲) اسکن فقط یک بار”آسیبپذیریهای جدید هر روز میآیند. راهحل: اسکن در CI برای هر build، و اسکن دورهای image های در حال اجرا.
۳) دادن docker.sock به اسکنر
Section titled “۳) دادن docker.sock به اسکنر”mount کردن /var/run/docker.sock به یک ابزار اسکن مرسوم است ولی (درس امنیت) کنترل کامل daemon را میدهد. راهحل: docker save و --input (مثال ۱).
۴) خطای دانلود پایگاه داده
Section titled “۴) خطای دانلود پایگاه داده”docker run --rm aquasec/trivy:latest image --quiet --input /nonexistent.tar --db-repository localhost:5999/no/db:2 2>&1 | grep -oE 'FATAL.*' | cut -c1-200 | head -1FATAL Fatal error run error: init error: DB error: failed to download vulnerability DB: OCI artifact error: failed to download vulnerability DB: failed to download artifact from localhost:5999/no/db:2وقتی پایگاه داده دانلود نشود (آدرس اشتباه، registry مسدود)، اسکن شروع نمیشود. راهحل: --db-repository دیگر، proxy برای daemon/کانتینر، یا کش پایگاه داده روی یک ماشین دارای دسترسی و انتقالش (مثل درس انتقال آفلاین).
۵) رد کردن همهی هشدارها
Section titled “۵) رد کردن همهی هشدارها”گذاشتن همهی CVE ها در لیست نادیدهگرفته (.trivyignore) بدون بررسی. راهحل: فقط مواردی را نادیده بگیر که واقعاً بررسی و دلیلش را نوشتهای.
بنویس CVE چیست و فرق CRITICAL با LOW در یک جمله؛ سپس با Trivy تعداد آسیبپذیریهای python:3.12-slim را با --severity CRITICAL بشمار.
دیدن جواب
CVE شناسهی عمومی یک آسیبپذیری شناختهشده است؛ CRITICAL خطر بسیار بالا و ساده برای سوءاستفاده و LOW خطر کم.
docker run --rm -v "$PWD/vs":/w -v lx-trivy-cache:/root/.cache aquasec/trivy:latest image --quiet --input /w/p312.tar --severity CRITICAL --format template --template '{{ range . }}{{ range .Vulnerabilities }}{{ println .VulnerabilityID }}{{ end }}{{ end }}' --db-repository ghcr.io/aquasecurity/trivy-db:2 2>/dev/null | grep -c CVE0تمرین اصلی: یک image قدیمی را اسکن و بهروزرسانی کن. alpine:3.12 را با Trivy اسکن کن و CRITICAL را بیاب، یک Dockerfile بنویس که با apk upgrade آن را رفع کند، دوباره build و اسکن کن و نشان بده تعداد CRITICAL صفر شد.
دیدن جواب
mkdir -p e2 && printf 'FROM alpine:3.12\nRUN apk upgrade --no-cache\n' > e2/DockerfileT() { docker run --rm -v "$PWD":/w -v lx-trivy-cache:/root/.cache aquasec/trivy:latest "$@" --db-repository ghcr.io/aquasecurity/trivy-db:2; }N() { T image --quiet --input /w/$1 --severity CRITICAL --format template --template '{{ range . }}{{ range .Vulnerabilities }}{{ println .VulnerabilityID }}{{ end }}{{ end }}' 2>/dev/null | grep -c CVE; }docker save -o old.tar alpine:3.12echo "قبل: $(N old.tar) مورد CRITICAL"docker build -q -t lx-vs-e2 e2 >/dev/null && docker save -o new.tar lx-vs-e2echo "بعد: $(N new.tar) مورد CRITICAL"docker rmi lx-vs-e2 >/dev/nullقبل: 1 مورد CRITICALبعد: 0 مورد CRITICALیک «دروازهی CI» بنویس: اسکریپتی که برای یک فهرست image (tar) اسکن میکند و اگر هرکدام CRITICAL داشت، نام آن را چاپ و در پایان با کد 1 تمام میشود. روی p38.tar و p312.tar تست کن (باید p38 رد و p312 قبول شود).
دیدن جواب
gate() { fail=0 for t in "$@"; do if docker run --rm -v "$PWD/vs":/w -v lx-trivy-cache:/root/.cache aquasec/trivy:latest image --quiet --input /w/$t.tar --severity CRITICAL --exit-code 1 --db-repository ghcr.io/aquasecurity/trivy-db:2 >/dev/null 2>&1; then echo "قبول: $t" else echo "رد: $t (CRITICAL دارد)"; fail=1 fi done return $fail}gate p38 p312; echo "کد خروج دروازه: $?"رد: p38 (CRITICAL دارد)قبول: p312کد خروج دروازه: 1آزمونک
Section titled “آزمونک”CVE چیست؟
قالب: CVE-سال-شماره.
ستون Fixed Version چه میگوید؟
اگر رفع دارد، بهروزرسانی راهحل است.
مؤثرترین کار ارزان برای کم کردن CVE ها؟
بیشتر CVE ها در سیستمعامل پایه و کتابخانههای قدیمیاند.
برای شکستن CI با Trivy کدام flag را میگذاری؟
اگر آسیبپذیری پیدا شود کد خروج ۱ میدهد.
چرا ترجیحاً docker.sock را به اسکنر نمیدهیم؟
اسکن tar ایمنتر است.
اسکن تمیز یعنی…
آسیبپذیریهای جدید هر روز میآیند.
جمعبندی
Section titled “جمعبندی”- CVE شناسهی آسیبپذیری شناختهشده؛ شدت: CRITICAL، HIGH، MEDIUM، LOW. به Fixed Version نگاه کن.
- Trivy:
docker save←trivy image --input x.tar؛ بدونdocker.sock. پایگاه داده از یک registry میآید (--db-repository) و کش میشود. - مؤثرترین رفع: base جدیدتر/کوچکتر و دوباره build؛ بعد دوباره اسکن.
- در CI:
--exit-code 1 --severity CRITICAL؛--ignore-unfixedبرای هشدارهای بیراهحل. trivy configDockerfile و Compose را بررسی میکند (USER، HEALTHCHECK).- Docker Scout مکمل است ولی به ورود و ارسال داده نیاز دارد. اسکن «امنبودن» را ثابت نمیکند و باید تکرار شود.
| دستور | کاری که میکند |
|---|---|
docker save -o app.tar myapp:1.0 | ذخیرهی image برای اسکن |
trivy image --input app.tar | اسکن image |
trivy image --severity CRITICAL,HIGH IMG | فقط شدتهای بالا |
trivy image --ignore-unfixed IMG | بدون موارد بیرفع |
trivy image --exit-code 1 --severity CRITICAL IMG | شکستن CI |
trivy config DIR | اسکن Dockerfile و Compose |
--db-repository ghcr.io/aquasecurity/trivy-db:2 | آدرس پایگاه داده |
docker scout cves IMG | Docker Scout (نیاز به login) — نمونه |