În postarea noastră anterioară, Am văzut cum să detectăm și să ne protejăm împotriva otrăvirii directe Pipeline Execuție (D-PPE). De asemenea, am văzut cum să detectăm această vulnerabilitate folosind Scaner Xygeni, precum și unele mecanisme de protecție.
Otrăvit Pipeline Execuție (EIP) este produs atunci când atacatorul poate modifica pipeline logic în oricare dintre cele două moduri:
- Prin modificarea fișierului de configurare CI ( pipeline) -> EIP direct (D-EIP)
- Prin modificarea fișierelor la care face referire pipeline (de exemplu: scripturi la care se face referire din interiorul pipeline fișier de configurare) -> EIP indirect (I-EIP)

În această postare, vom analiza în detaliu PPE indirect. Dar, înainte de asta și ca o completare la postarea mea anterioară, haideți să vedem mai întâi cum gestionează GitHub execuția... pipelineși care sunt mecanismele de protecție împotriva D-PPE.
Cum protejează GitHub execuția pipelineVine de la PR-uri?
Cum funcționează GitHub în ceea ce privește execuția modificărilor pipelines?
Modificat pipelinepot proveni din împingeri sau Pull Requests (RELATII CU PUBLICUL). Ca o practică recomandată, se recomandă insistent evitarea oricărei „împingeri” directe către o ramură protejată și utilizarea Pull Requests ca mecanism de impunere a unei anumite revizuiri înainte de acceptarea oricărui cod contribuit.
Pull Requests poate proveni din două surse diferite:
- PR-uri provenite de la furci
- PR-uri provenite de la ramuri
PR-uri de la furci poate proveni fie de la public or privat arhive.
Întrucât avem de-a face cu EIP (echipament individual de protecție) Pipeline Executare), punctul nostru principal nu este „acceptarea” unei RP, ci executarea uneia modificate pipeline în timpul procesului de acceptare/aprobare al PR-ului. În centrul unui atac PPE se află executarea neintenționată a unui atac modificat „rău intenționat” pipeline.
În câteva cuvinte, otrăvit Pipeline Execuția (PPE) este produsă atunci când atacatorul poate modifica pipeline logică.
Sunt două variante:
- EIP direct (D-PPE): Într-un scenariu D-PPE, Atacatorul modifică fișierul de configurare CI într-un repozitoriu la care au acces, fie prin trimiterea modificării direct către o ramură la distanță neprotejată din repozitoriu, fie prin trimiterea unui PR cu modificarea dintr-o ramură sau o bifurcație. De la CI pipeline Execuția este definită de comenzile din fișierul de configurare CI modificat, comenzile malițioase ale atacatorului se execută în cele din urmă în nodul de compilare odată ce compilarea pipeline este declanșat.
- EIP indirect (I-PPE): În anumite cazuri, posibilitatea D-PPE nu este disponibilă unui adversar cu acces la un SCM depozit (de exemplu, dacă pipeline este configurat să extragă fișierul de configurare CI dintr-o ramură separată, protejată, din același depozit). Într-un astfel de scenariu, în loc să otrăvim pipeline în sine, un atacator injectează cod rău intenționat în fișierele la care face referire pipeline (de exemplu: scripturi la care se face referire din interiorul pipeline fișier de configurare)
În ambele cazuri, GitHub va executa codul modificat pipeline fără a fi nevoie de o revizuire sau aprobare prealabilă.
PR-uri de la furculițe pe public odihnă
GitHub permite configurarea comportamentului la procesare PR-uri provenite de la bifurcații în depozitele publice.
Când o cerere de aprobare (PR) vine de la o bifurcație, GitHub forțează întotdeauna un anumit nivel de „aprobare” înainte de a o executa. pipeline asociat cu PR-ulAcest nivel de aprobare variază de la o aprobare slabă la una strictă.
At Nivel organizațional (Org>>Setări>>Acțiuni>>General), puteți alege între mai multe opțiuni de „aprobare”:

Cel mai strict este ultimul („Necesită aprobarea tuturor colaboratorilor externi„”) deoarece GitHub va necesita întotdeauna aprobare atunci când PR-ul provine de la fork-uri de la colaboratori externi.
Dar chiar și în acest caz strict, există diferențe între colaboratorii cu permisiuni de citire și scriere.
- Când PR-ul provine de la un citit utilizatorul, executarea pipeline este OPRIT până când există o aprobare a modificărilor. Dacă aprobarea este în regulă, atunci modificările pipeline este executat.
- Când PR-ul provine de la un scrie utilizatorul, aprobarea nu este necesară și modificarea pipeline este întotdeauna executat!!

În concluzie, PR-urile provenite de la fork-uri pe repozitorii publice sunt ușor protejate împotriva PPE. Există o oarecare protecție împotriva utilizatorilor externi (de citire), dar nimic legat de utilizatorii interni (de scriere).
Ce ziceti PR-uri provenite de la furci din depozite private?
PR-uri de la furculițe pe privat odihnă
În acest scenariu, GitHub oferă câteva setări de configurare utile.

Setările de mai sus pot fi configurate fie la organ sau la repo nivel.
Atunci când nicio opțiune nu este bifată, GitHub va cere aprobarea și nu va executa modificarea pipelineAceasta este cea mai sigură configurație!!
configurația cea mai nesigură este când Execută fluxuri de lucru din furcă pull request„este bifat”În acest caz, același lucru se întâmplă atât pentru utilizatorii care citesc, cât și pentru cei care scriu, Github va executa automat modificarea. pipeline!! Și această situație poate fi chiar mai rău dacă "Trimiteți token-uri de scriere către fluxuri de lucru din fork pull requests"Și"Trimiteți secrete și variabile către fluxuri de lucru din fork pull requests„sunt bifate. Nu faceți acest lucru decât dacă este justificat în mod clar!!”
Dacă "Necesită aprobare pentru furcă pull request fluxuri de lucru„”, situația de mai sus este oarecum îmbunătățită: GitHub va cere aprobarea și nu va executa modificarea pipeline pentru utilizatorul de citire, dar îl va executa în continuare pentru un utilizator de scriere.

Furculițe văzute, ce zici de PR-uri provenite de la sucursale?
PR-uri de la ramuri
Pentru a proteja acest scenariu, trebuie să vă bazați pe Reguli de protecție a sucursalei.
La nivel de depozit, puteți crea reguli de protecție a ramurilor pentru orice ramură. Aceste reguli adaugă câteva constrângeri privind modificarea ramurilor protejate.
Deși configurați o regulă pentru a „Necesită a pull request înainte de fuziune"Și"Necesită aprobări", cel modificat pipeline va fi executat automat la crearea PR-ului„Aprobarea” se va aplica numai acțiunii de îmbinare.

Dar otrăvirea indirectă Pipeline Execuție
După cum am văzut mai sus, D-PPE poate fi atenuat prin utilizarea cerere_de_tragere_țintă, dar nu se aplică EIP-urilor.
Dacă utilizați pull_request_target, verificarea implicită va fi codul de bază. Dar dacă doriți să validați anumite verificări asupra codului contribuit (cod PR), trebuie să verificați explicit codul PR. Prin urmare, dacă codul PR a modificat vreun script shell invocat de pipeline, „baza” (seiful) pipeline va invoca scriptul shell „modificat” → PPE indirect!!
Soluția la aceasta este puțin mai complicată (nu există o soluție magică precum pull_request_target).
Gama de pipeline este acum sigur pentru D-PPE deoarece folosim pull_request_target. Dar este încă vulnerabil la I-PPE.
În exemplul nostru de test, trebuie să verificăm codul PR practic pentru a realiza compilarea, dar testele sunt executate asupra artefactului generat de compilare.
Deci .. De ce nu verifici ambele baze de cod?
- Cod PR de checkout, deoarece este codul contribuit pe care dorim să îl construim și să îl testăm.
- Cod de bază pentru a rula versiunea originală a pipeline și scripturile de construire/testare
Acest lucru s-ar putea face prin verificarea acelor baze de cod în foldere diferiteCodul de bază ar putea fi extras în folderul rădăcină, iar PR-ul într-un alt folder. În acest caz, am executa scriptul de compilare și de testare din folderul rădăcină pe baza codului plasat în noul folder.
Aceasta este o soluție ușoară, desigur!! Dar, în scopuri didactice, aș dori să introduc o variantă destul de interesantă (…)
GitHub execuție_flux_de_trebuire eveniment declanșator
În afară de cerere_de_tragere_țintă, GitHub oferă un alt eveniment declanșator: execuție_flux_de_trebuireAcest eveniment permite executarea unei pipeline condiționat de altul pipelineexecuția lui.
execuție_flux_de_trebuire și cerere_de_tragere_țintă declanșatoarele sunt similare într-un aspect: ambele vor fi executate în mod privilegiat și, în ciuda modificărilor PR, baza pipeline va fi executat!!
Să vedem actualul nostru pipeline:
name: PR TARGET CI
on:
pull_request_target:
branches: [ main ]
env:
MY_SECRET: ${{ secrets.MY_SECRET }}
jobs:
prt_build_test_and_merge:
runs-on: ubuntu-latest
steps:
# checkout PR code
- name: Checkout repository
uses: actions/checkout@v4
with:
# This is to get the PR code instead of the repo code
ref: ${{ github.event.pull_request.head.sha }}
# Simulation of a compilation
- name: Building ...
run: |
mkdir ./bin
touch ./bin/mybin.exe
ls -lR
# Simulation of running tests
- name: Running tests ...
id : run_tests
run: |
echo Running tests..
chmod +x runtests.sh
./runtests.sh
echo Tests executed.
#
# Let’s omit the check conditions at this moment …
#
- name: pr_check_conditions_to_merge
[...]
Secțiunea de construcție este sigură pentru D-PPE, dar secțiunea de testare este încă vulnerabilă la I-PPE.
pipeline în sine este sigur pentru D-PPE datorită cerere_de_tragere_țintă declanșator. Dar pasul de testare este încă vulnerabil la I-PPE din cauza invocării unui script shell extern.
Evitarea EIP-ului
Scopul celor de mai sus pipeline este de a construi și testa codul contribuit, fiind sigur pentru EIP.
Deci .. De ce nu împărțiți pipeline în două? Unul pentru construcție și altul pentru testare..
- 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.

În acest fel:
- 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ă vedem 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):
ame: Test CI
on:
workflow_run:
workflows: [ 'PR TARGET 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.
#
# Let’s omit the check conditions at this moment …
#
- name: pr_check_conditions_to_merge
[...]
Uau... bună soluție!! Dar... Suntem în siguranță? Mă tem că nu 😭
Într-adevăr, am introdus o nouă vulnerabilitate!! Care dintre ele? Acesta va fi subiectul următoarei noastre postări 🙂 … Rămâneți pe fază!!
PS: Îmi pare rău, nu pot să tac 🤐 ..Ai auzit de Intoxicație cu artefacte ? 😂







